diff --git a/CHANGES.txt b/CHANGES.txt index ec4d34a39..0d1f612ea 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,10 @@ +4.12.0 (May 15, 2024) +- Added support for targeting rules based on semantic versions (https://semver.org/). +- Added the logic to handle correctly when the SDK receives an unsupported Matcher type. +- Enhanced SDK Headers for Authorization Frameworks +- Cleaned unused imports and renaming some methods +- Fixed empty token handler thanks to @hpark-miovision + 4.11.1 (Feb 29, 2024) - Fixed deadlock in UniqueKeysTracker when sending Unique Keys. @@ -9,7 +16,7 @@ - Added getTreatmentsByFlagSets without attributes. - Fixed some issues for flag sets: Not logging a warning when using flag sets that don't contain cached feature flags. -4.10.1 (Nov 9, 2023) +4.10.1 (Nov 8, 2023) - Fixed handler for response http headers. 4.10.0 (Nov 2, 2023) diff --git a/client/pom.xml b/client/pom.xml index 131fe248c..b48aa1100 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,7 +5,7 @@ io.split.client java-client-parent - 4.11.1 + 4.12.0 java-client jar diff --git a/client/src/main/java/io/split/Spec.java b/client/src/main/java/io/split/Spec.java new file mode 100644 index 000000000..9e03a59ab --- /dev/null +++ b/client/src/main/java/io/split/Spec.java @@ -0,0 +1,11 @@ +package io.split; + +public final class Spec { + + private Spec() { + // restrict instantiation + } + + public static final String SPEC_VERSION = "1.1"; +} + diff --git a/client/src/main/java/io/split/client/CustomHeaderDecorator.java b/client/src/main/java/io/split/client/CustomHeaderDecorator.java new file mode 100644 index 000000000..934c43681 --- /dev/null +++ b/client/src/main/java/io/split/client/CustomHeaderDecorator.java @@ -0,0 +1,15 @@ +package io.split.client; + +import io.split.client.dtos.RequestContext; + +import java.util.Map; +import java.util.List; + +public interface CustomHeaderDecorator +{ + /** + * Get the additional headers needed for all http operations + * @return HashMap of addition headers + */ + Map> getHeaderOverrides(RequestContext context); +} diff --git a/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java b/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java index 84bf09509..bd365590c 100644 --- a/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java +++ b/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java @@ -2,26 +2,23 @@ import com.google.common.annotations.VisibleForTesting; import io.split.client.dtos.SegmentChange; +import io.split.client.dtos.SplitHttpResponse; import io.split.client.utils.Json; import io.split.client.utils.Utils; import io.split.engine.common.FetchOptions; import io.split.engine.segments.SegmentChangeFetcher; +import io.split.service.SplitHttpClient; import io.split.telemetry.domain.enums.HTTPLatenciesEnum; import io.split.telemetry.domain.enums.LastSynchronizationRecordsEnum; import io.split.telemetry.domain.enums.ResourceEnum; import io.split.telemetry.storage.TelemetryRuntimeProducer; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.HttpStatus; -import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import static com.google.common.base.Preconditions.checkNotNull; @@ -33,20 +30,17 @@ public final class HttpSegmentChangeFetcher implements SegmentChangeFetcher { private static final String SINCE = "since"; private static final String TILL = "till"; - private static final String PREFIX = "segmentChangeFetcher"; - private static final String CACHE_CONTROL_HEADER_NAME = "Cache-Control"; - private static final String CACHE_CONTROL_HEADER_VALUE = "no-cache"; - private final CloseableHttpClient _client; + private final SplitHttpClient _client; private final URI _target; private final TelemetryRuntimeProducer _telemetryRuntimeProducer; - public static HttpSegmentChangeFetcher create(CloseableHttpClient client, URI root, TelemetryRuntimeProducer telemetryRuntimeProducer) + public static HttpSegmentChangeFetcher create(SplitHttpClient client, URI root, TelemetryRuntimeProducer telemetryRuntimeProducer) throws URISyntaxException { return new HttpSegmentChangeFetcher(client, Utils.appendPath(root, "api/segmentChanges"), telemetryRuntimeProducer); } - private HttpSegmentChangeFetcher(CloseableHttpClient client, URI uri, TelemetryRuntimeProducer telemetryRuntimeProducer) { + private HttpSegmentChangeFetcher(SplitHttpClient client, URI uri, TelemetryRuntimeProducer telemetryRuntimeProducer) { _client = client; _target = uri; checkNotNull(_target); @@ -57,8 +51,6 @@ private HttpSegmentChangeFetcher(CloseableHttpClient client, URI uri, TelemetryR public SegmentChange fetch(String segmentName, long since, FetchOptions options) { long start = System.currentTimeMillis(); - CloseableHttpResponse response = null; - try { String path = _target.getPath() + "/" + segmentName; URIBuilder uriBuilder = new URIBuilder(_target) @@ -69,45 +61,27 @@ public SegmentChange fetch(String segmentName, long since, FetchOptions options) } URI uri = uriBuilder.build(); - HttpGet request = new HttpGet(uri); - - if(options.cacheControlHeadersEnabled()) { - request.setHeader(CACHE_CONTROL_HEADER_NAME, CACHE_CONTROL_HEADER_VALUE); - } - - response = _client.execute(request); - - int statusCode = response.getCode(); - if (_log.isDebugEnabled()) { - _log.debug(String.format("[%s] %s. Status code: %s", request.getMethod(), uri.toURL(), statusCode)); - } + SplitHttpResponse response = _client.get(uri, options, null); - if (statusCode < HttpStatus.SC_OK || statusCode >= HttpStatus.SC_MULTIPLE_CHOICES) { - _telemetryRuntimeProducer.recordSyncError(ResourceEnum.SEGMENT_SYNC, statusCode); - _log.error(String.format("Response status was: %s. Reason: %s", statusCode , response.getReasonPhrase())); - if (statusCode == HttpStatus.SC_FORBIDDEN) { + if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { + _telemetryRuntimeProducer.recordSyncError(ResourceEnum.SEGMENT_SYNC, response.statusCode()); + if (response.statusCode() == HttpStatus.SC_FORBIDDEN) { _log.error("factory instantiation: you passed a client side type sdkKey, " + "please grab an sdk key from the Split user interface that is of type server side"); } throw new IllegalStateException(String.format("Could not retrieve segment changes for %s, since %s; http return code %s", - segmentName, since, statusCode)); + segmentName, since, response.statusCode())); } - _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.SEGMENTS, System.currentTimeMillis()); - String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); - - return Json.fromJson(json, SegmentChange.class); + return Json.fromJson(response.body(), SegmentChange.class); } catch (Exception e) { throw new IllegalStateException(String.format("Error occurred when trying to sync segment: %s, since: %s. Details: %s", segmentName, since, e), e); } finally { _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.SEGMENTS, System.currentTimeMillis()-start); - Utils.forceClose(response); } - - } @VisibleForTesting diff --git a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java index 65d37d144..9f8d2036b 100644 --- a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java +++ b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java @@ -1,29 +1,28 @@ package io.split.client; import com.google.common.annotations.VisibleForTesting; + import io.split.client.dtos.SplitChange; +import io.split.client.dtos.SplitHttpResponse; import io.split.client.exceptions.UriTooLongException; import io.split.client.utils.Json; import io.split.client.utils.Utils; import io.split.engine.common.FetchOptions; import io.split.engine.experiments.SplitChangeFetcher; +import io.split.service.SplitHttpClient; import io.split.telemetry.domain.enums.HTTPLatenciesEnum; import io.split.telemetry.domain.enums.ResourceEnum; import io.split.telemetry.storage.TelemetryRuntimeProducer; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.HttpStatus; -import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import static com.google.common.base.Preconditions.checkNotNull; +import static io.split.Spec.SPEC_VERSION; /** * Created by adilaijaz on 5/30/15. @@ -34,20 +33,17 @@ public final class HttpSplitChangeFetcher implements SplitChangeFetcher { private static final String SINCE = "since"; private static final String TILL = "till"; private static final String SETS = "sets"; - - private static final String HEADER_CACHE_CONTROL_NAME = "Cache-Control"; - private static final String HEADER_CACHE_CONTROL_VALUE = "no-cache"; - - private final CloseableHttpClient _client; + private static final String SPEC = "s"; + private final SplitHttpClient _client; private final URI _target; private final TelemetryRuntimeProducer _telemetryRuntimeProducer; - public static HttpSplitChangeFetcher create(CloseableHttpClient client, URI root, TelemetryRuntimeProducer telemetryRuntimeProducer) + public static HttpSplitChangeFetcher create(SplitHttpClient client, URI root, TelemetryRuntimeProducer telemetryRuntimeProducer) throws URISyntaxException { return new HttpSplitChangeFetcher(client, Utils.appendPath(root, "api/splitChanges"), telemetryRuntimeProducer); } - private HttpSplitChangeFetcher(CloseableHttpClient client, URI uri, TelemetryRuntimeProducer telemetryRuntimeProducer) { + private HttpSplitChangeFetcher(SplitHttpClient client, URI uri, TelemetryRuntimeProducer telemetryRuntimeProducer) { _client = client; _target = uri; checkNotNull(_target); @@ -64,49 +60,34 @@ public SplitChange fetch(long since, FetchOptions options) { long start = System.currentTimeMillis(); - CloseableHttpResponse response = null; - try { - URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SINCE, "" + since); - if (options.hasCustomCN()) { - uriBuilder.addParameter(TILL, "" + options.targetCN()); - } + URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + SPEC_VERSION); + uriBuilder.addParameter(SINCE, "" + since); if (!options.flagSetsFilter().isEmpty()) { uriBuilder.addParameter(SETS, "" + options.flagSetsFilter()); } - URI uri = uriBuilder.build(); - - HttpGet request = new HttpGet(uri); - if(options.cacheControlHeadersEnabled()) { - request.setHeader(HEADER_CACHE_CONTROL_NAME, HEADER_CACHE_CONTROL_VALUE); - } - - response = _client.execute(request); - - int statusCode = response.getCode(); - - if (_log.isDebugEnabled()) { - _log.debug(String.format("[%s] %s. Status code: %s", request.getMethod(), uri.toURL(), statusCode)); + if (options.hasCustomCN()) { + uriBuilder.addParameter(TILL, "" + options.targetCN()); } + URI uri = uriBuilder.build(); + SplitHttpResponse response = _client.get(uri, options, null); - if (statusCode < HttpStatus.SC_OK || statusCode >= HttpStatus.SC_MULTIPLE_CHOICES) { - _telemetryRuntimeProducer.recordSyncError(ResourceEnum.SPLIT_SYNC, statusCode); - if (statusCode == HttpStatus.SC_REQUEST_URI_TOO_LONG) { + if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { + if (response.statusCode() == HttpStatus.SC_REQUEST_URI_TOO_LONG) { _log.error("The amount of flag sets provided are big causing uri length error."); - throw new UriTooLongException(String.format("Status code: %s. Message: %s", statusCode, response.getReasonPhrase())); + throw new UriTooLongException(String.format("Status code: %s. Message: %s", response.statusCode(), response.statusMessage())); } - _log.warn(String.format("Response status was: %s. Reason: %s", statusCode , response.getReasonPhrase())); - throw new IllegalStateException(String.format("Could not retrieve splitChanges since %s; http return code %s", since, statusCode)); + _telemetryRuntimeProducer.recordSyncError(ResourceEnum.SPLIT_SYNC, response.statusCode()); + throw new IllegalStateException( + String.format("Could not retrieve splitChanges since %s; http return code %s", since, response.statusCode()) + ); } - String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); - - return Json.fromJson(json, SplitChange.class); + return Json.fromJson(response.body(), SplitChange.class); } catch (Exception e) { throw new IllegalStateException(String.format("Problem fetching splitChanges since %s: %s", since, e), e); } finally { _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.SPLITS, System.currentTimeMillis()-start); - Utils.forceClose(response); } } diff --git a/client/src/main/java/io/split/client/NoOpHeaderDecorator.java b/client/src/main/java/io/split/client/NoOpHeaderDecorator.java new file mode 100644 index 000000000..8ce04fdbc --- /dev/null +++ b/client/src/main/java/io/split/client/NoOpHeaderDecorator.java @@ -0,0 +1,18 @@ +package io.split.client; + +import io.split.client.CustomHeaderDecorator; +import io.split.client.dtos.RequestContext; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class NoOpHeaderDecorator implements CustomHeaderDecorator { + public NoOpHeaderDecorator() { + } + + @Override + public Map> getHeaderOverrides(RequestContext context) { + return new HashMap<>(); + } +} diff --git a/client/src/main/java/io/split/client/RequestDecorator.java b/client/src/main/java/io/split/client/RequestDecorator.java new file mode 100644 index 000000000..1572463ef --- /dev/null +++ b/client/src/main/java/io/split/client/RequestDecorator.java @@ -0,0 +1,77 @@ +package io.split.client; + +import io.split.client.dtos.RequestContext; + +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.Header; + +import java.util.HashSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Set; +import java.util.List; + +public final class RequestDecorator { + CustomHeaderDecorator _headerDecorator; + + private static final Set forbiddenHeaders = new HashSet<>(Arrays.asList( + "splitsdkversion", + "splitmachineip", + "splitmachinename", + "splitimpressionsmode", + "host", + "referrer", + "content-type", + "content-length", + "content-encoding", + "accept", + "keep-alive", + "x-fastly-debug")); + + public RequestDecorator(CustomHeaderDecorator headerDecorator) { + _headerDecorator = (headerDecorator == null) + ? new NoOpHeaderDecorator() + : headerDecorator; + } + + public HttpRequest decorateHeaders(HttpRequest request) { + try { + Map> headers = _headerDecorator + .getHeaderOverrides(new RequestContext(convertToMap(request.getHeaders()))); + for (Map.Entry> entry : headers.entrySet()) { + if (isHeaderAllowed(entry.getKey())) { + List values = entry.getValue(); + for (int i = 0; i < values.size(); i++) { + if (i == 0) { + request.setHeader(entry.getKey(), values.get(i)); + } else { + request.addHeader(entry.getKey(), values.get(i)); + } + } + } + } + } catch (Exception e) { + throw new IllegalArgumentException( + String.format("Problem adding custom headers to request decorator: %s", e), e); + } + + return request; + } + + private boolean isHeaderAllowed(String headerName) { + return !forbiddenHeaders.contains(headerName.toLowerCase()); + } + + private Map> convertToMap(Header[] to_convert) { + Map> to_return = new HashMap>(); + for (Integer i = 0; i < to_convert.length; i++) { + if (!to_return.containsKey(to_convert[i].getName())) { + to_return.put(to_convert[i].getName(), new ArrayList()); + } + to_return.get(to_convert[i].getName()).add(to_convert[i].getValue()); + } + return to_return; + } +} diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index 11e2ec2bc..2f29c1719 100644 --- a/client/src/main/java/io/split/client/SplitClientConfig.java +++ b/client/src/main/java/io/split/client/SplitClientConfig.java @@ -13,9 +13,9 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.io.InputStream; import java.util.Properties; import java.util.concurrent.ThreadFactory; +import java.io.InputStream; import static io.split.inputValidation.FlagSetsValidator.cleanup; @@ -90,6 +90,8 @@ public class SplitClientConfig { private final long _lastSeenCacheSize; private final HashSet _flagSetsFilter; private final int _invalidSets; + private final CustomHeaderDecorator _customHeaderDecorator; + public static Builder builder() { return new Builder(); @@ -145,7 +147,8 @@ private SplitClientConfig(String endpoint, long lastSeenCacheSize, ThreadFactory threadFactory, HashSet flagSetsFilter, - int invalidSets) { + int invalidSets, + CustomHeaderDecorator customHeaderDecorator) { _endpoint = endpoint; _eventsEndpoint = eventsEndpoint; _featuresRefreshRate = pollForFeatureChangesEveryNSeconds; @@ -197,6 +200,7 @@ private SplitClientConfig(String endpoint, _threadFactory = threadFactory; _flagSetsFilter = flagSetsFilter; _invalidSets = invalidSets; + _customHeaderDecorator = customHeaderDecorator; Properties props = new Properties(); try { @@ -355,9 +359,17 @@ public String telemetryURL() { return _telemetryURL; } + /** + * @deprecated As of release 4.X.X, replaced by {@link #getTelemetryRefreshRate()} } //todo update version + **/ + @Deprecated public int get_telemetryRefreshRate() { return _telemetryRefreshRate; } + + public int getTelemetryRefreshRate() { + return _telemetryRefreshRate; + } public int streamingRetryDelay() {return _onDemandFetchRetryDelayMs;} public int streamingFetchMaxRetries() {return _onDemandFetchMaxRetries;} @@ -393,6 +405,10 @@ public int getInvalidSets() { return _invalidSets; } + public CustomHeaderDecorator customHeaderDecorator() { + return _customHeaderDecorator; + } + public static final class Builder { private String _endpoint = SDK_ENDPOINT; @@ -449,6 +465,7 @@ public static final class Builder { private ThreadFactory _threadFactory; private HashSet _flagSetsFilter = new HashSet<>(); private int _invalidSetsCount = 0; + private CustomHeaderDecorator _customHeaderDecorator = null; public Builder() { } @@ -932,6 +949,17 @@ public Builder flagSetsFilter(List flagSetsFilter) { return this; } + /** + * User Custom Header Decorator + * + * @param customHeaderDecorator + * @return this builder + */ + public Builder customHeaderDecorator(CustomHeaderDecorator customHeaderDecorator) { + _customHeaderDecorator = customHeaderDecorator; + return this; + } + /** * Thread Factory * @@ -1091,7 +1119,8 @@ public SplitClientConfig build() { _lastSeenCacheSize, _threadFactory, _flagSetsFilter, - _invalidSetsCount); + _invalidSetsCount, + _customHeaderDecorator); } } } \ 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 9adfc55e3..a783b1445 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -26,7 +26,6 @@ import io.split.client.impressions.strategy.ProcessImpressionNone; import io.split.client.impressions.strategy.ProcessImpressionOptimized; import io.split.client.impressions.strategy.ProcessImpressionStrategy; -import io.split.client.interceptors.AuthorizationInterceptorFilter; import io.split.client.interceptors.ClientKeyInterceptorFilter; import io.split.client.interceptors.FlagSetsFilter; import io.split.client.interceptors.FlagSetsFilterImpl; @@ -58,6 +57,8 @@ import io.split.engine.segments.SegmentChangeFetcher; import io.split.engine.segments.SegmentSynchronizationTaskImp; import io.split.integrations.IntegrationsConfig; +import io.split.service.SplitHttpClient; +import io.split.service.SplitHttpClientImpl; import io.split.storages.SegmentCache; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SegmentCacheProducer; @@ -120,8 +121,9 @@ public class SplitFactoryImpl implements SplitFactory { private static final Logger _log = LoggerFactory.getLogger(SplitFactory.class); - private static final String LEGACY_LOG_MESSAGE = "The sdk initialize in localhost mode using Legacy file. The splitFile or " + - "inputStream doesn't add it to the config."; + private static final String LEGACY_LOG_MESSAGE = "The sdk initialize in localhost mode using Legacy file. The splitFile or " + + + "inputStream doesn't add it to the config."; private final static long SSE_CONNECT_TIMEOUT = 30000; private final static long SSE_SOCKET_TIMEOUT = 70000; @@ -134,7 +136,7 @@ public class SplitFactoryImpl implements SplitFactory { private final SplitClient _client; private final SplitManager _manager; - //Cache + // Cache private final SplitCacheConsumer _splitCache; private final SegmentCacheConsumer _segmentCache; @@ -146,21 +148,21 @@ public class SplitFactoryImpl implements SplitFactory { private final SDKMetadata _sdkMetadata; private OperationMode _operationMode; - //Depending on mode are not mandatory + // Depending on mode are not mandatory private final TelemetrySyncTask _telemetrySyncTask; private final SegmentSynchronizationTaskImp _segmentSynchronizationTaskImp; private final SplitFetcher _splitFetcher; private final SplitSynchronizationTask _splitSynchronizationTask; private final EventsTask _eventsTask; private final SyncManager _syncManager; - private final CloseableHttpClient _httpclient; + private final SplitHttpClient _splitHttpClient; private final UserStorageWrapper _userStorageWrapper; private final ImpressionsSender _impressionsSender; private final URI _rootTarget; private final URI _eventsRootTarget; private final UniqueKeysTracker _uniqueKeysTracker; - //Constructor for standalone mode + // Constructor for standalone mode public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException { _userStorageWrapper = null; _operationMode = config.operationMode(); @@ -174,16 +176,19 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn _telemetryStorageProducer = telemetryStorage; if (config.blockUntilReady() == -1) { - //BlockUntilReady not been set - _log.warn("no setBlockUntilReadyTimeout parameter has been set - incorrect control treatments could be logged” " + - "if no ready config has been set when building factory"); + // BlockUntilReady not been set + _log.warn( + "no setBlockUntilReadyTimeout parameter has been set - incorrect control treatments could be logged” " + + + "if no ready config has been set when building factory"); } // SDKReadinessGates _gates = new SDKReadinessGates(); // HttpClient - _httpclient = buildHttpClient(apiToken, config, _sdkMetadata); + RequestDecorator requestDecorator = new RequestDecorator(config.customHeaderDecorator()); + _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, requestDecorator); // Roots _rootTarget = URI.create(config.endpoint()); @@ -196,7 +201,8 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn ImpressionsStorage impressionsStorage = new InMemoryImpressionsStorage(config.impressionsQueueSize()); _splitCache = splitCache; _segmentCache = segmentCache; - _telemetrySynchronizer = new TelemetryInMemorySubmitter(_httpclient, URI.create(config.telemetryURL()), telemetryStorage, + _telemetrySynchronizer = new TelemetryInMemorySubmitter(_splitHttpClient, URI.create(config.telemetryURL()), + telemetryStorage, splitCache, _segmentCache, telemetryStorage, _startTime); // Segments @@ -212,11 +218,12 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn config.featuresRefreshRate(), config.getThreadFactory()); - //ImpressionSender - _impressionsSender = HttpImpressionsSender.create(_httpclient, URI.create(config.eventsEndpoint()), config.impressionsMode(), + // ImpressionSender + _impressionsSender = HttpImpressionsSender.create(_splitHttpClient, URI.create(config.eventsEndpoint()), + config.impressionsMode(), _telemetryStorageProducer); - //UniqueKeysTracker + // UniqueKeysTracker _uniqueKeysTracker = createUniqueKeysTracker(config); // Impressions @@ -224,10 +231,10 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn // EventClient EventsStorage eventsStorage = new InMemoryEventsStorage(config.eventsQueueSize(), _telemetryStorageProducer); - EventsSender eventsSender = EventsSender.create(_httpclient, _eventsRootTarget, _telemetryStorageProducer); - _eventsTask = EventsTask.create(config.eventSendIntervalInMillis(), eventsStorage, eventsSender, config.getThreadFactory()); - - _telemetrySyncTask = new TelemetrySyncTask(config.get_telemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory()); + EventsSender eventsSender = EventsSender.create(_splitHttpClient, _eventsRootTarget, _telemetryStorageProducer); + _eventsTask = EventsTask.create(config.eventSendIntervalInMillis(), eventsStorage, eventsSender, + config.getThreadFactory()); + _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory()); // Evaluator _evaluator = new EvaluatorImp(splitCache, segmentCache); @@ -240,8 +247,8 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn config, _gates, _evaluator, - _telemetryStorageProducer, //TelemetryEvaluation instance - _telemetryStorageProducer, //TelemetryConfiguration instance + _telemetryStorageProducer, // TelemetryEvaluation instance + _telemetryStorageProducer, // TelemetryConfiguration instance flagSetsFilter); // SplitManager @@ -250,10 +257,11 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn // SyncManager SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp, _impressionsManager, _eventsTask, _telemetrySyncTask, _uniqueKeysTracker); - SplitAPI splitAPI = SplitAPI.build(_httpclient, buildSSEdHttpClient(apiToken, config, _sdkMetadata)); + SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata), requestDecorator); _syncManager = SyncManagerImp.build(splitTasks, _splitFetcher, splitCache, splitAPI, - segmentCache, _gates, _telemetryStorageProducer, _telemetrySynchronizer, config, splitParser, flagSetsFilter); + segmentCache, _gates, _telemetryStorageProducer, _telemetrySynchronizer, config, splitParser, + flagSetsFilter); _syncManager.start(); // DestroyOnShutDown @@ -267,26 +275,32 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn } } - //Constructor for consumer mode - protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStorageWrapper customStorageWrapper) throws URISyntaxException { - //Variables that are not used in Consumer mode. + // Constructor for consumer mode + protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStorageWrapper customStorageWrapper) + throws URISyntaxException { + // Variables that are not used in Consumer mode. _segmentSynchronizationTaskImp = null; _splitFetcher = null; _splitSynchronizationTask = null; _eventsTask = null; - _httpclient = null; + _splitHttpClient = null; _rootTarget = null; _eventsRootTarget = null; Metadata metadata = new Metadata(config.ipAddressEnabled(), SplitClientConfig.splitSdkVersion); _userStorageWrapper = new UserStorageWrapper(customStorageWrapper); - UserCustomSegmentAdapterConsumer userCustomSegmentAdapterConsumer= new UserCustomSegmentAdapterConsumer(customStorageWrapper); - UserCustomSplitAdapterConsumer userCustomSplitAdapterConsumer = new UserCustomSplitAdapterConsumer(customStorageWrapper); - // TODO migrate impressions sender to Task instead manager and not instantiate Producer here. + UserCustomSegmentAdapterConsumer userCustomSegmentAdapterConsumer = new UserCustomSegmentAdapterConsumer( + customStorageWrapper); + UserCustomSplitAdapterConsumer userCustomSplitAdapterConsumer = new UserCustomSplitAdapterConsumer( + customStorageWrapper); + // TODO migrate impressions sender to Task instead manager and not instantiate + // Producer here. UserCustomImpressionAdapterConsumer userCustomImpressionAdapterConsumer = new UserCustomImpressionAdapterConsumer(); - UserCustomImpressionAdapterProducer userCustomImpressionAdapterProducer = new UserCustomImpressionAdapterProducer(customStorageWrapper, + UserCustomImpressionAdapterProducer userCustomImpressionAdapterProducer = new UserCustomImpressionAdapterProducer( + customStorageWrapper, metadata); - UserCustomEventAdapterProducer userCustomEventAdapterProducer = new UserCustomEventAdapterProducer(customStorageWrapper, metadata); + UserCustomEventAdapterProducer userCustomEventAdapterProducer = new UserCustomEventAdapterProducer( + customStorageWrapper, metadata); _operationMode = config.operationMode(); _sdkMetadata = createSdkMetadata(config.ipAddressEnabled(), SplitClientConfig.splitSdkVersion); @@ -300,9 +314,11 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _segmentCache = userCustomSegmentAdapterConsumer; if (config.blockUntilReady() == -1) { - //BlockUntilReady not been set - _log.warn("no setBlockUntilReadyTimeout parameter has been set - incorrect control treatments could be logged” " + - "if no ready config has been set when building factory"); + // BlockUntilReady not been set + _log.warn( + "no setBlockUntilReadyTimeout parameter has been set - incorrect control treatments could be logged” " + + + "if no ready config has been set when building factory"); } // SDKReadinessGates @@ -313,7 +329,7 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _impressionsSender = PluggableImpressionSender.create(customStorageWrapper); _uniqueKeysTracker = createUniqueKeysTracker(config); _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, userCustomImpressionAdapterProducer); - _telemetrySyncTask = new TelemetrySyncTask(config.get_telemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory()); + _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory()); SplitTasks splitTasks = SplitTasks.build(null, null, _impressionsManager, null, _telemetrySyncTask, _uniqueKeysTracker); @@ -321,9 +337,11 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor // Synchronizer Synchronizer synchronizer = new ConsumerSynchronizer(splitTasks); FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>()); - if(!config.getSetsFilter().isEmpty()) { - _log.warn("FlagSets filter is not applicable for Consumer modes where the SDK does not keep rollout data in sync. FlagSet " + - "filter was discarded"); + if (!config.getSetsFilter().isEmpty()) { + _log.warn( + "FlagSets filter is not applicable for Consumer modes where the SDK does not keep rollout data in sync. FlagSet " + + + "filter was discarded"); } _client = new SplitClientImpl(this, userCustomSplitAdapterConsumer, @@ -332,11 +350,10 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor config, _gates, _evaluator, - _telemetryStorageProducer, //TelemetryEvaluation instance - _telemetryStorageProducer, //TelemetryConfiguration instance + _telemetryStorageProducer, // TelemetryEvaluation instance + _telemetryStorageProducer, // TelemetryConfiguration instance flagSetsFilter); - // SyncManager _syncManager = new ConsumerSyncManager(synchronizer); _syncManager.start(); @@ -355,7 +372,7 @@ protected SplitFactoryImpl(SplitClientConfig config) { _telemetrySynchronizer = null; _telemetrySyncTask = null; _eventsTask = null; - _httpclient = null; + _splitHttpClient = null; _impressionsSender = null; _rootTarget = null; _eventsRootTarget = null; @@ -369,29 +386,31 @@ protected SplitFactoryImpl(SplitClientConfig config) { _gates = new SDKReadinessGates(); _segmentCache = segmentCache; - //SegmentFetcher + // SegmentFetcher SegmentChangeFetcher segmentChangeFetcher = new LocalhostSegmentFetcherNoop(); - if(config.segmentDirectory() != null){ + if (config.segmentDirectory() != null) { segmentChangeFetcher = new LocalhostSegmentChangeFetcher(config.segmentDirectory()); } - _segmentSynchronizationTaskImp = new SegmentSynchronizationTaskImp(segmentChangeFetcher, + _segmentSynchronizationTaskImp = new SegmentSynchronizationTaskImp(segmentChangeFetcher, config.segmentsRefreshRate(), config.numThreadsForSegmentFetch(), segmentCache, _telemetryStorageProducer, _splitCache, - config.getThreadFactory()); + config.getThreadFactory()); // SplitFetcher SplitChangeFetcher splitChangeFetcher = createSplitChangeFetcher(config); SplitParser splitParser = new SplitParser(); - _splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCache, _telemetryStorageProducer, flagSetsFilter); + _splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCache, _telemetryStorageProducer, + flagSetsFilter); // SplitSynchronizationTask - _splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher, splitCache, config.featuresRefreshRate(), config.getThreadFactory()); + _splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher, splitCache, + config.featuresRefreshRate(), config.getThreadFactory()); _impressionsManager = new ImpressionsManager.NoOpImpressionsManager(); @@ -411,12 +430,13 @@ protected SplitFactoryImpl(SplitClientConfig config) { config, _gates, _evaluator, - _telemetryStorageProducer, //TelemetryEvaluation instance - _telemetryStorageProducer, //TelemetryConfiguration instance + _telemetryStorageProducer, // TelemetryEvaluation instance + _telemetryStorageProducer, // TelemetryConfiguration instance flagSetsFilter); // Synchronizer - Synchronizer synchronizer = new LocalhostSynchronizer(splitTasks, _splitFetcher, config.localhostRefreshEnabled()); + Synchronizer synchronizer = new LocalhostSynchronizer(splitTasks, _splitFetcher, + config.localhostRefreshEnabled()); // SplitManager _manager = new SplitManagerImpl(splitCache, config, _gates, _telemetryStorageProducer); @@ -454,9 +474,9 @@ public synchronized void destroy() { _log.info("Shutdown called for split"); _syncManager.shutdown(); _log.info("Successful shutdown of syncManager"); - if(OperationMode.STANDALONE.equals(_operationMode)) { + if (OperationMode.STANDALONE.equals(_operationMode)) { _telemetryStorageProducer.recordSessionLength(System.currentTimeMillis() - _startTime); - } else if(OperationMode.CONSUMER.equals(_operationMode)) { + } else if (OperationMode.CONSUMER.equals(_operationMode)) { _userStorageWrapper.disconnect(); } } catch (IOException e) { @@ -471,7 +491,9 @@ public boolean isDestroyed() { return isTerminated; } - private static CloseableHttpClient buildHttpClient(String apiToken, SplitClientConfig config, SDKMetadata sdkMetadata) { + private static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config, + SDKMetadata sdkMetadata, RequestDecorator requestDecorator) + throws URISyntaxException { SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() .setSslContext(SSLContexts.createSystemDefault()) .setTlsVersions(TLS.V_1_1, TLS.V_1_2) @@ -495,8 +517,6 @@ private static CloseableHttpClient buildHttpClient(String apiToken, SplitClientC HttpClientBuilder httpClientbuilder = HttpClients.custom() .setConnectionManager(cm) .setDefaultRequestConfig(requestConfig) - .addRequestInterceptorLast(AuthorizationInterceptorFilter.instance(apiToken)) - .addRequestInterceptorLast(SdkMetadataInterceptorFilter.instance(sdkMetadata)) .addRequestInterceptorLast(new GzipEncoderRequestInterceptor()) .addResponseInterceptorLast((new GzipDecoderResponseInterceptor())); @@ -505,10 +525,14 @@ private static CloseableHttpClient buildHttpClient(String apiToken, SplitClientC httpClientbuilder = setupProxy(httpClientbuilder, config); } - return httpClientbuilder.build(); + return SplitHttpClientImpl.create(httpClientbuilder.build(), + requestDecorator, + apiToken, + sdkMetadata); } - private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitClientConfig config, SDKMetadata sdkMetadata) { + private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitClientConfig config, + SDKMetadata sdkMetadata) { RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(Timeout.ofMilliseconds(SSE_CONNECT_TIMEOUT)) .build(); @@ -550,17 +574,20 @@ private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder, _log.debug("Proxy setup using credentials"); BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); AuthScope siteScope = new AuthScope(config.proxy().getHostName(), config.proxy().getPort()); - Credentials siteCreds = new UsernamePasswordCredentials(config.proxyUsername(), config.proxyPassword().toCharArray()); + Credentials siteCreds = new UsernamePasswordCredentials(config.proxyUsername(), + config.proxyPassword().toCharArray()); credsProvider.setCredentials(siteScope, siteCreds); httpClientbuilder.setDefaultCredentialsProvider(credsProvider); } - return httpClientbuilder; + return httpClientbuilder; } - private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config, SegmentCacheProducer segmentCacheProducer, - SplitCacheConsumer splitCacheConsumer) throws URISyntaxException { - SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(_httpclient, _rootTarget, _telemetryStorageProducer); + private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config, + SegmentCacheProducer segmentCacheProducer, + SplitCacheConsumer splitCacheConsumer) throws URISyntaxException { + SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(_splitHttpClient, _rootTarget, + _telemetryStorageProducer); return new SegmentSynchronizationTaskImp(segmentChangeFetcher, config.segmentsRefreshRate(), @@ -571,14 +598,17 @@ private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config, Se config.getThreadFactory()); } - private SplitFetcher buildSplitFetcher(SplitCacheProducer splitCacheProducer, SplitParser splitParser, FlagSetsFilter flagSetsFilter) throws - URISyntaxException { - SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(_httpclient, _rootTarget, _telemetryStorageProducer); - return new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, _telemetryStorageProducer,flagSetsFilter); + private SplitFetcher buildSplitFetcher(SplitCacheProducer splitCacheProducer, SplitParser splitParser, + FlagSetsFilter flagSetsFilter) throws URISyntaxException { + SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(_splitHttpClient, _rootTarget, + _telemetryStorageProducer); + return new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, _telemetryStorageProducer, + flagSetsFilter); } - private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config, ImpressionsStorageConsumer impressionsStorageConsumer, - ImpressionsStorageProducer impressionsStorageProducer) throws URISyntaxException { + private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config, + ImpressionsStorageConsumer impressionsStorageConsumer, + ImpressionsStorageProducer impressionsStorageProducer) throws URISyntaxException { List impressionListeners = new ArrayList<>(); if (config.integrationsConfig() != null) { config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.ASYNC).stream() @@ -591,13 +621,15 @@ private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config, } ProcessImpressionStrategy processImpressionStrategy = null; ImpressionCounter counter = null; - ImpressionListener listener = !impressionListeners.isEmpty() ? new ImpressionListener.FederatedImpressionListener(impressionListeners) + ImpressionListener listener = !impressionListeners.isEmpty() + ? new ImpressionListener.FederatedImpressionListener(impressionListeners) : null; - switch (config.impressionsMode()){ + switch (config.impressionsMode()) { case OPTIMIZED: counter = new ImpressionCounter(); ImpressionObserver impressionObserver = new ImpressionObserver(config.getLastSeenCacheSize()); - processImpressionStrategy = new ProcessImpressionOptimized(listener != null, impressionObserver, counter, _telemetryStorageProducer); + processImpressionStrategy = new ProcessImpressionOptimized(listener != null, impressionObserver, + counter, _telemetryStorageProducer); break; case DEBUG: impressionObserver = new ImpressionObserver(config.getLastSeenCacheSize()); @@ -608,7 +640,8 @@ private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config, processImpressionStrategy = new ProcessImpressionNone(listener != null, _uniqueKeysTracker, counter); break; } - return ImpressionsManagerImpl.instance(config, _telemetryStorageProducer, impressionsStorageConsumer, impressionsStorageProducer, + return ImpressionsManagerImpl.instance(config, _telemetryStorageProducer, impressionsStorageConsumer, + impressionsStorageProducer, _impressionsSender, processImpressionStrategy, counter, listener); } @@ -629,9 +662,10 @@ private SDKMetadata createSdkMetadata(boolean ipAddressEnabled, String splitSdkV } private void manageSdkReady(SplitClientConfig config) { - ExecutorService executorService = buildExecutorService(config.getThreadFactory(), "SPLIT-SDKReadyForConsumer-%d"); + ExecutorService executorService = buildExecutorService(config.getThreadFactory(), + "SPLIT-SDKReadyForConsumer-%d"); executorService.submit(() -> { - while(!_userStorageWrapper.connect()) { + while (!_userStorageWrapper.connect()) { try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { @@ -640,16 +674,18 @@ private void manageSdkReady(SplitClientConfig config) { } } _gates.sdkInternalReady(); - _telemetrySynchronizer.synchronizeConfig(config, System.currentTimeMillis(), ApiKeyCounter.getApiKeyCounterInstance(). - getFactoryInstances(), new ArrayList<>()); + _telemetrySynchronizer.synchronizeConfig(config, System.currentTimeMillis(), + ApiKeyCounter.getApiKeyCounterInstance().getFactoryInstances(), new ArrayList<>()); }); } - private UniqueKeysTracker createUniqueKeysTracker(SplitClientConfig config){ - if (config.impressionsMode().equals(ImpressionsManager.Mode.NONE)){ - int uniqueKeysRefreshRate = config.operationMode().equals(OperationMode.STANDALONE) ? config.uniqueKeysRefreshRateInMemory() + private UniqueKeysTracker createUniqueKeysTracker(SplitClientConfig config) { + if (config.impressionsMode().equals(ImpressionsManager.Mode.NONE)) { + int uniqueKeysRefreshRate = config.operationMode().equals(OperationMode.STANDALONE) + ? config.uniqueKeysRefreshRateInMemory() : config.uniqueKeysRefreshRateRedis(); - return new UniqueKeysTrackerImp(_telemetrySynchronizer, uniqueKeysRefreshRate, config.filterUniqueKeysRefreshRate(), + return new UniqueKeysTrackerImp(_telemetrySynchronizer, uniqueKeysRefreshRate, + config.filterUniqueKeysRefreshRate(), config.getThreadFactory()); } return null; @@ -670,7 +706,7 @@ private SplitChangeFetcher createSplitChangeFetcher(SplitClientConfig splitClien inputStreamProvider = new StaticContentInputStreamProvider(inputStream); } try { - switch (fileType){ + switch (fileType) { case JSON: return new JsonLocalhostSplitChangeFetcher(inputStreamProvider); case YAML: @@ -682,10 +718,13 @@ private SplitChangeFetcher createSplitChangeFetcher(SplitClientConfig splitClien } } catch (Exception e) { _log.warn(String.format("There was no file named %s found. " + - "We created a split client that returns default treatments for all feature flags for all of your users. " + - "If you wish to return a specific treatment for a feature flag, enter the name of that feature flag name and " + - "treatment name separated by whitespace in %s; one pair per line. Empty lines or lines starting with '#' are " + - "considered comments", + "We created a split client that returns default treatments for all feature flags for all of your users. " + + + "If you wish to return a specific treatment for a feature flag, enter the name of that feature flag name and " + + + "treatment name separated by whitespace in %s; one pair per line. Empty lines or lines starting with '#' are " + + + "considered comments", splitFile, splitFile), e); } _log.warn(LEGACY_LOG_MESSAGE); @@ -704,4 +743,4 @@ private FileTypeEnum getFileTypeFromFileName(String fileName) { } } -} \ No newline at end of file +} diff --git a/client/src/main/java/io/split/client/dtos/BetweenStringMatcherData.java b/client/src/main/java/io/split/client/dtos/BetweenStringMatcherData.java new file mode 100644 index 000000000..3af1cc0c1 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/BetweenStringMatcherData.java @@ -0,0 +1,11 @@ +package io.split.client.dtos; + +/** + * Metadata to support the between matcher. + * + * @author adil + */ +public class BetweenStringMatcherData { + public String start; + public String end; +} diff --git a/client/src/main/java/io/split/client/dtos/Matcher.java b/client/src/main/java/io/split/client/dtos/Matcher.java index 12c824d2c..fc2c65155 100644 --- a/client/src/main/java/io/split/client/dtos/Matcher.java +++ b/client/src/main/java/io/split/client/dtos/Matcher.java @@ -13,6 +13,7 @@ public class Matcher { public WhitelistMatcherData whitelistMatcherData; public UnaryNumericMatcherData unaryNumericMatcherData; public BetweenMatcherData betweenMatcherData; + public BetweenStringMatcherData betweenStringMatcherData; public DependencyMatcherData dependencyMatcherData; public Boolean booleanMatcherData; public String stringMatcherData; diff --git a/client/src/main/java/io/split/client/dtos/MatcherType.java b/client/src/main/java/io/split/client/dtos/MatcherType.java index ec22baec7..b8c78a7bd 100644 --- a/client/src/main/java/io/split/client/dtos/MatcherType.java +++ b/client/src/main/java/io/split/client/dtos/MatcherType.java @@ -30,5 +30,12 @@ public enum MatcherType { EQUAL_TO_BOOLEAN, /* Dependency Matcher */ - IN_SPLIT_TREATMENT + IN_SPLIT_TREATMENT, + + /* Semver matchers */ + EQUAL_TO_SEMVER, + GREATER_THAN_OR_EQUAL_TO_SEMVER, + LESS_THAN_OR_EQUAL_TO_SEMVER, + IN_LIST_SEMVER, + BETWEEN_SEMVER } diff --git a/client/src/main/java/io/split/client/dtos/RequestContext.java b/client/src/main/java/io/split/client/dtos/RequestContext.java new file mode 100644 index 000000000..29fb7f77b --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/RequestContext.java @@ -0,0 +1,20 @@ +package io.split.client.dtos; + +import java.util.Map; +import java.util.List; +/** + * A structure returning a context for RequestDecorator class + */ + +public class RequestContext +{ + private final Map> _headers; + + public RequestContext(Map> headers) { + _headers = headers; + } + + public Map> headers() { + return _headers; + } +} diff --git a/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java b/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java new file mode 100644 index 000000000..a5474cf5b --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java @@ -0,0 +1,34 @@ +package io.split.client.dtos; + +import java.util.Map; +import org.apache.hc.core5.http.Header; +/** + * A structure for returning http call results information + */ +public class SplitHttpResponse { + private final Integer _statusCode; + private final String _statusMessage; + private final String _body; + private final Header[] _responseHeaders; + + public SplitHttpResponse(Integer statusCode, String statusMessage, String body, Header[] headers) { + _statusCode = statusCode; + _statusMessage = statusMessage; + _body = body; + _responseHeaders = headers; + } + public Integer statusCode() { + return _statusCode; + } + public String statusMessage() { + return _statusMessage; + } + + public String body() { + return _body; + } + + public Header[] responseHeaders() { + return _responseHeaders; + } +} diff --git a/client/src/main/java/io/split/client/events/EventsSender.java b/client/src/main/java/io/split/client/events/EventsSender.java index 64ab3e90c..2c023ef3f 100644 --- a/client/src/main/java/io/split/client/events/EventsSender.java +++ b/client/src/main/java/io/split/client/events/EventsSender.java @@ -4,9 +4,9 @@ import io.split.client.dtos.Event; import io.split.client.utils.Utils; import io.split.service.HttpPostImp; +import io.split.service.SplitHttpClient; import io.split.telemetry.domain.enums.HttpParamsWrapper; import io.split.telemetry.storage.TelemetryRuntimeProducer; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import java.net.URI; import java.net.URISyntaxException; @@ -18,20 +18,20 @@ public class EventsSender { private static final String BULK_ENDPOINT_PATH = "api/events/bulk"; private final URI _bulkEndpoint; - private final CloseableHttpClient _client; + private final SplitHttpClient _client; private final TelemetryRuntimeProducer _telemetryRuntimeProducer; private final HttpPostImp _httpPostImp; - public static EventsSender create(CloseableHttpClient httpclient, URI eventsTarget, TelemetryRuntimeProducer telemetryRuntimeProducer) + public static EventsSender create(SplitHttpClient splitHttpclient, URI eventsTarget, TelemetryRuntimeProducer telemetryRuntimeProducer) throws URISyntaxException { - return new EventsSender(httpclient, Utils.appendPath(eventsTarget, BULK_ENDPOINT_PATH), telemetryRuntimeProducer); + return new EventsSender(splitHttpclient, Utils.appendPath(eventsTarget, BULK_ENDPOINT_PATH), telemetryRuntimeProducer); } - EventsSender(CloseableHttpClient httpclient, URI eventsTarget, TelemetryRuntimeProducer telemetryRuntimeProducer) { - _client = checkNotNull(httpclient); + EventsSender(SplitHttpClient splitHttpclient, URI eventsTarget, TelemetryRuntimeProducer telemetryRuntimeProducer) { + _client = splitHttpclient; _bulkEndpoint = checkNotNull(eventsTarget); _telemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); - _httpPostImp = new HttpPostImp(httpclient, telemetryRuntimeProducer); + _httpPostImp = new HttpPostImp(_client, telemetryRuntimeProducer); } public void sendEvents(List _data) { diff --git a/client/src/main/java/io/split/client/exceptions/SemverParseException.java b/client/src/main/java/io/split/client/exceptions/SemverParseException.java new file mode 100644 index 000000000..892ba7535 --- /dev/null +++ b/client/src/main/java/io/split/client/exceptions/SemverParseException.java @@ -0,0 +1,7 @@ +package io.split.client.exceptions; + +public class SemverParseException extends Exception { + public SemverParseException(String message) { + super(message); + } +} diff --git a/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java b/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java index 0a08e5dad..06df64cc4 100644 --- a/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java +++ b/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java @@ -2,16 +2,15 @@ import com.google.common.annotations.VisibleForTesting; import io.split.client.dtos.ImpressionCount; +import io.split.client.dtos.SplitHttpResponse; import io.split.client.dtos.TestImpressions; import io.split.client.utils.Utils; +import io.split.service.SplitHttpClient; import io.split.telemetry.domain.enums.HTTPLatenciesEnum; import io.split.telemetry.domain.enums.LastSynchronizationRecordsEnum; import io.split.telemetry.domain.enums.ResourceEnum; import io.split.telemetry.storage.TelemetryRuntimeProducer; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpStatus; import org.slf4j.Logger; @@ -20,8 +19,10 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; @@ -36,14 +37,15 @@ public class HttpImpressionsSender implements ImpressionsSender { private static final Logger _logger = LoggerFactory.getLogger(HttpImpressionsSender.class); - private final CloseableHttpClient _client; + private final SplitHttpClient _client; private final URI _impressionBulkTarget; private final URI _impressionCountTarget; private final ImpressionsManager.Mode _mode; private final TelemetryRuntimeProducer _telemetryRuntimeProducer; - public static HttpImpressionsSender create(CloseableHttpClient client, URI eventsRootEndpoint, ImpressionsManager.Mode mode, - TelemetryRuntimeProducer telemetryRuntimeProducer) throws URISyntaxException { + public static HttpImpressionsSender create(SplitHttpClient client, URI eventsRootEndpoint, + ImpressionsManager.Mode mode, + TelemetryRuntimeProducer telemetryRuntimeProducer) throws URISyntaxException { return new HttpImpressionsSender(client, Utils.appendPath(eventsRootEndpoint, BULK_ENDPOINT_PATH), Utils.appendPath(eventsRootEndpoint, COUNT_ENDPOINT_PATH), @@ -51,8 +53,9 @@ public static HttpImpressionsSender create(CloseableHttpClient client, URI event telemetryRuntimeProducer); } - private HttpImpressionsSender(CloseableHttpClient client, URI impressionBulkTarget, URI impressionCountTarget, ImpressionsManager.Mode mode, - TelemetryRuntimeProducer telemetryRuntimeProducer) { + private HttpImpressionsSender(SplitHttpClient client, URI impressionBulkTarget, URI impressionCountTarget, + ImpressionsManager.Mode mode, + TelemetryRuntimeProducer telemetryRuntimeProducer) { _client = client; _mode = mode; _impressionBulkTarget = impressionBulkTarget; @@ -62,33 +65,25 @@ private HttpImpressionsSender(CloseableHttpClient client, URI impressionBulkTarg @Override public void postImpressionsBulk(List impressions) { - - CloseableHttpResponse response = null; long initTime = System.currentTimeMillis(); try { HttpEntity entity = Utils.toJsonEntity(impressions); + Map> additionalHeaders = Collections.singletonMap(IMPRESSIONS_MODE_HEADER, + Collections.singletonList(_mode.toString())); + SplitHttpResponse response = _client.post(_impressionBulkTarget, entity, additionalHeaders); - HttpPost request = new HttpPost(_impressionBulkTarget); - request.addHeader(IMPRESSIONS_MODE_HEADER, _mode.toString()); - request.setEntity(entity); - - response = _client.execute(request); - - int status = response.getCode(); - - if (status < HttpStatus.SC_OK || status >= HttpStatus.SC_MULTIPLE_CHOICES) { - _telemetryRuntimeProducer.recordSyncError(ResourceEnum.IMPRESSION_SYNC, status); - _logger.warn(String.format("Response status was: %s. Reason: %s", status , response.getReasonPhrase())); + if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { + _telemetryRuntimeProducer.recordSyncError(ResourceEnum.IMPRESSION_SYNC, response.statusCode()); } - _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.IMPRESSIONS, System.currentTimeMillis()); + _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.IMPRESSIONS, + System.currentTimeMillis()); } catch (Throwable t) { _logger.warn("Exception when posting impressions" + impressions, t); } finally { - _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.IMPRESSIONS, System.currentTimeMillis() - initTime); - Utils.forceClose(response); + _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.IMPRESSIONS, + System.currentTimeMillis() - initTime); } - } @Override @@ -99,16 +94,18 @@ public void postCounters(HashMap raw) { return; } - HttpPost request = new HttpPost(_impressionCountTarget); - request.setEntity(Utils.toJsonEntity(ImpressionCount.fromImpressionCounterData(raw))); - try (CloseableHttpResponse response = _client.execute(request)) { - int status = response.getCode(); - if (status < HttpStatus.SC_OK || status >= HttpStatus.SC_MULTIPLE_CHOICES) { - _telemetryRuntimeProducer.recordSyncError(ResourceEnum.IMPRESSION_COUNT_SYNC, status); - _logger.warn(String.format("Response status was: %s. Reason: %s", status , response.getReasonPhrase())); + try { + SplitHttpResponse response = _client.post(_impressionCountTarget, + Utils.toJsonEntity(ImpressionCount.fromImpressionCounterData(raw)), + null); + + if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { + _telemetryRuntimeProducer.recordSyncError(ResourceEnum.IMPRESSION_COUNT_SYNC, response.statusCode()); } - _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.IMPRESSIONS_COUNT, System.currentTimeMillis() - initTime); - _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.IMPRESSIONS_COUNT, System.currentTimeMillis()); + _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.IMPRESSIONS_COUNT, + System.currentTimeMillis() - initTime); + _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.IMPRESSIONS_COUNT, + System.currentTimeMillis()); } catch (IOException exc) { _logger.warn("Exception when posting impression counters: ", exc); } diff --git a/client/src/main/java/io/split/engine/SDKReadinessGates.java b/client/src/main/java/io/split/engine/SDKReadinessGates.java index b4fd99fe6..c5ca5dbc8 100644 --- a/client/src/main/java/io/split/engine/SDKReadinessGates.java +++ b/client/src/main/java/io/split/engine/SDKReadinessGates.java @@ -3,9 +3,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/client/src/main/java/io/split/engine/common/PushManagerImp.java b/client/src/main/java/io/split/engine/common/PushManagerImp.java index ff3343ed4..b6118efb6 100644 --- a/client/src/main/java/io/split/engine/common/PushManagerImp.java +++ b/client/src/main/java/io/split/engine/common/PushManagerImp.java @@ -83,7 +83,7 @@ public static PushManagerImp build(Synchronizer synchronizer, PushStatusTracker pushStatusTracker = new PushStatusTrackerImp(statusMessages, telemetryRuntimeProducer); return new PushManagerImp(new AuthApiClientImp(authUrl, splitAPI.getHttpClient(), telemetryRuntimeProducer), EventSourceClientImp.build(streamingUrl, featureFlagsWorker, segmentWorker, pushStatusTracker, splitAPI.getSseHttpClient(), - telemetryRuntimeProducer, threadFactory), + telemetryRuntimeProducer, threadFactory, splitAPI.getRequestDecorator()), featureFlagsWorker, segmentWorker, pushStatusTracker, diff --git a/client/src/main/java/io/split/engine/common/SplitAPI.java b/client/src/main/java/io/split/engine/common/SplitAPI.java index 00b0c0e58..adb0500de 100644 --- a/client/src/main/java/io/split/engine/common/SplitAPI.java +++ b/client/src/main/java/io/split/engine/common/SplitAPI.java @@ -1,25 +1,30 @@ package io.split.engine.common; +import io.split.client.RequestDecorator; +import io.split.service.SplitHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SplitAPI { - private final CloseableHttpClient _httpClient; + private final SplitHttpClient _httpClient; private final CloseableHttpClient _sseHttpClient; + private final RequestDecorator _requestDecorator; private static final Logger _log = LoggerFactory.getLogger(SplitAPI.class); - private SplitAPI(CloseableHttpClient httpClient, CloseableHttpClient sseHttpClient) { + private SplitAPI(SplitHttpClient httpClient, CloseableHttpClient sseHttpClient, RequestDecorator requestDecorator) { _httpClient = httpClient; _sseHttpClient = sseHttpClient; + _requestDecorator = requestDecorator; } - public static SplitAPI build(CloseableHttpClient httpClient, CloseableHttpClient sseHttpClient){ - return new SplitAPI(httpClient,sseHttpClient); + public static SplitAPI build(SplitHttpClient httpClient, CloseableHttpClient sseHttpClient, + RequestDecorator requestDecorator) { + return new SplitAPI(httpClient, sseHttpClient, requestDecorator); } - public CloseableHttpClient getHttpClient() { + public SplitHttpClient getHttpClient() { return _httpClient; } @@ -27,16 +32,20 @@ public CloseableHttpClient getSseHttpClient() { return _sseHttpClient; } - public void close(){ + public RequestDecorator getRequestDecorator() { + return _requestDecorator; + } + + public void close() { try { _httpClient.close(); - } catch (Exception e){ - _log.error("Error trying to close httpcClient", e); + } catch (Exception e) { + _log.error("Error trying to close regular http client", e); } try { _sseHttpClient.close(); - } catch (Exception e){ + } catch (Exception e) { _log.error("Error trying to close sseHttpClient", e); } } -} \ No newline at end of file +} diff --git a/client/src/main/java/io/split/engine/common/SplitTasks.java b/client/src/main/java/io/split/engine/common/SplitTasks.java index 6045e826e..d2960d824 100644 --- a/client/src/main/java/io/split/engine/common/SplitTasks.java +++ b/client/src/main/java/io/split/engine/common/SplitTasks.java @@ -5,11 +5,8 @@ import io.split.client.impressions.UniqueKeysTracker; import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.SegmentSynchronizationTask; -import io.split.engine.segments.SegmentSynchronizationTaskImp; import io.split.telemetry.synchronizer.TelemetrySyncTask; -import static com.google.common.base.Preconditions.checkNotNull; - public class SplitTasks { private final SplitSynchronizationTask _splitSynchronizationTask; private final SegmentSynchronizationTask _segmentSynchronizationTask; diff --git a/client/src/main/java/io/split/engine/evaluator/Labels.java b/client/src/main/java/io/split/engine/evaluator/Labels.java index b51a4e14b..ac5a465a9 100644 --- a/client/src/main/java/io/split/engine/evaluator/Labels.java +++ b/client/src/main/java/io/split/engine/evaluator/Labels.java @@ -6,4 +6,5 @@ public class Labels { public static final String KILLED = "killed"; public static final String DEFINITION_NOT_FOUND = "definition not found"; public static final String EXCEPTION = "exception"; + public static final String UNSUPPORTED_MATCHER = "targeting rule type unsupported by sdk"; } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/experiments/SplitParser.java b/client/src/main/java/io/split/engine/experiments/SplitParser.java index d5ae12f15..b320dff66 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitParser.java +++ b/client/src/main/java/io/split/engine/experiments/SplitParser.java @@ -6,6 +6,9 @@ import io.split.client.dtos.MatcherGroup; import io.split.client.dtos.Partition; import io.split.client.dtos.Split; +import io.split.client.dtos.ConditionType; +import io.split.client.dtos.MatcherType; +import io.split.engine.evaluator.Labels; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.AttributeMatcher; import io.split.engine.matchers.BetweenMatcher; @@ -16,6 +19,7 @@ import io.split.engine.matchers.GreaterThanOrEqualToMatcher; import io.split.engine.matchers.LessThanOrEqualToMatcher; import io.split.engine.matchers.UserDefinedSegmentMatcher; +import io.split.engine.matchers.EqualToSemverMatcher; import io.split.engine.matchers.collections.ContainsAllOfSetMatcher; import io.split.engine.matchers.collections.ContainsAnyOfSetMatcher; import io.split.engine.matchers.collections.EqualToSetMatcher; @@ -25,6 +29,11 @@ import io.split.engine.matchers.strings.RegularExpressionMatcher; import io.split.engine.matchers.strings.StartsWithAnyOfMatcher; import io.split.engine.matchers.strings.WhitelistMatcher; +import io.split.engine.matchers.GreaterThanOrEqualToSemverMatcher; +import io.split.engine.matchers.LessThanOrEqualToSemverMatcher; +import io.split.engine.matchers.InListSemverMatcher; +import io.split.engine.matchers.BetweenSemverMatcher; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +68,12 @@ private ParsedSplit parseWithoutExceptionHandling(Split split) { for (Condition condition : split.conditions) { List partitions = condition.partitions; + if (checkUnsupportedMatcherExist(condition.matcherGroup.matchers)) { + _log.error("Unsupported matcher type found for feature flag: " + split.name + " , will revert to default template matcher."); + parsedConditionList.clear(); + parsedConditionList.add(getTemplateCondition()); + break; + } CombiningMatcher matcher = toMatcher(condition.matcherGroup); parsedConditionList.add(new ParsedCondition(condition.conditionType, matcher, partitions, condition.label)); } @@ -67,6 +82,34 @@ private ParsedSplit parseWithoutExceptionHandling(Split split) { split.changeNumber, split.trafficAllocation, split.trafficAllocationSeed, split.algo, split.configurations, split.sets); } + private boolean checkUnsupportedMatcherExist(List matchers) { + MatcherType typeCheck = null; + for (io.split.client.dtos.Matcher matcher : matchers) { + typeCheck = null; + try { + typeCheck = matcher.matcherType; + } catch (NullPointerException e) { + // If the exception is caught, it means unsupported matcher + break; + } + } + if (typeCheck != null) return false; + return true; + } + + private ParsedCondition getTemplateCondition() { + List templatePartitions = Lists.newArrayList(); + Partition partition = new Partition(); + partition.treatment = "control"; + partition.size = 100; + templatePartitions.add(partition); + return new ParsedCondition( + ConditionType.ROLLOUT, + CombiningMatcher.of(new AllKeysMatcher()), + templatePartitions, + Labels.UNSUPPORTED_MATCHER); + } + private CombiningMatcher toMatcher(MatcherGroup matcherGroup) { List matchers = matcherGroup.matchers; checkArgument(!matchers.isEmpty()); @@ -156,6 +199,26 @@ private AttributeMatcher toMatcher(Matcher matcher) { + ". matcher.booleanMatcherData() MUST NOT BE null"); delegate = new BooleanMatcher(matcher.booleanMatcherData); break; + case EQUAL_TO_SEMVER: + checkNotNull(matcher.stringMatcherData, "stringMatcherData is required for EQUAL_TO_SEMVER matcher type"); + delegate = new EqualToSemverMatcher(matcher.stringMatcherData); + break; + case GREATER_THAN_OR_EQUAL_TO_SEMVER: + checkNotNull(matcher.stringMatcherData, "stringMatcherData is required for GREATER_THAN_OR_EQUAL_TO_SEMVER matcher type"); + delegate = new GreaterThanOrEqualToSemverMatcher(matcher.stringMatcherData); + break; + case LESS_THAN_OR_EQUAL_TO_SEMVER: + checkNotNull(matcher.stringMatcherData, "stringMatcherData is required for LESS_THAN_OR_EQUAL_SEMVER matcher type"); + delegate = new LessThanOrEqualToSemverMatcher(matcher.stringMatcherData); + break; + case IN_LIST_SEMVER: + checkNotNull(matcher.whitelistMatcherData, "whitelistMatcherData is required for IN_LIST_SEMVER matcher type"); + delegate = new InListSemverMatcher(matcher.whitelistMatcherData.whitelist); + break; + case BETWEEN_SEMVER: + checkNotNull(matcher.betweenStringMatcherData, "betweenStringMatcherData is required for BETWEEN_SEMVER matcher type"); + delegate = new BetweenSemverMatcher(matcher.betweenStringMatcherData.start, matcher.betweenStringMatcherData.end); + break; default: throw new IllegalArgumentException("Unknown matcher type: " + matcher.matcherType); } diff --git a/client/src/main/java/io/split/engine/matchers/AllKeysMatcher.java b/client/src/main/java/io/split/engine/matchers/AllKeysMatcher.java index 54de94ce0..790224ab1 100644 --- a/client/src/main/java/io/split/engine/matchers/AllKeysMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/AllKeysMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/matchers/AttributeMatcher.java b/client/src/main/java/io/split/engine/matchers/AttributeMatcher.java index 487a5d3de..92deb0140 100644 --- a/client/src/main/java/io/split/engine/matchers/AttributeMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/AttributeMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import java.util.Map; import java.util.Objects; diff --git a/client/src/main/java/io/split/engine/matchers/BetweenMatcher.java b/client/src/main/java/io/split/engine/matchers/BetweenMatcher.java index 79ccd676c..a0ccfc1b7 100644 --- a/client/src/main/java/io/split/engine/matchers/BetweenMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/BetweenMatcher.java @@ -2,7 +2,6 @@ import io.split.client.dtos.DataType; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/matchers/BetweenSemverMatcher.java b/client/src/main/java/io/split/engine/matchers/BetweenSemverMatcher.java new file mode 100644 index 000000000..326e21830 --- /dev/null +++ b/client/src/main/java/io/split/engine/matchers/BetweenSemverMatcher.java @@ -0,0 +1,58 @@ +package io.split.engine.matchers; + +import io.split.engine.evaluator.EvaluationContext; + +import java.util.Map; + +public class BetweenSemverMatcher implements Matcher { + + private final Semver _semverStart; + private final Semver _semverEnd; + + public BetweenSemverMatcher(String semverStart, String semverEnd) { + _semverStart = Semver.build(semverStart); + _semverEnd = Semver.build(semverEnd); + } + + @Override + public boolean match(Object matchValue, String bucketingKey, Map attributes, EvaluationContext evaluationContext) { + if (!(matchValue instanceof String) || _semverStart == null || _semverEnd == null) { + return false; + } + Semver matchSemver = Semver.build(matchValue.toString()); + if (matchSemver == null) { + return false; + } + + return matchSemver.compare(_semverStart) >= 0 && matchSemver.compare(_semverEnd) <= 0; + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("between semver "); + bldr.append(_semverStart.version()); + bldr.append(" and "); + bldr.append(_semverEnd.version()); + return bldr.toString(); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + _semverStart.hashCode() + _semverEnd.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof BetweenSemverMatcher)) return false; + + BetweenSemverMatcher other = (BetweenSemverMatcher) obj; + + return _semverStart == other._semverStart && _semverEnd == other._semverEnd; + } + +} diff --git a/client/src/main/java/io/split/engine/matchers/BooleanMatcher.java b/client/src/main/java/io/split/engine/matchers/BooleanMatcher.java index 0a7418bb7..79d5a303f 100644 --- a/client/src/main/java/io/split/engine/matchers/BooleanMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/BooleanMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/matchers/CombiningMatcher.java b/client/src/main/java/io/split/engine/matchers/CombiningMatcher.java index da75c53f3..4097ef851 100644 --- a/client/src/main/java/io/split/engine/matchers/CombiningMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/CombiningMatcher.java @@ -2,10 +2,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import io.split.client.SplitClientImpl; import io.split.client.dtos.MatcherCombiner; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import java.util.List; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/matchers/EqualToMatcher.java b/client/src/main/java/io/split/engine/matchers/EqualToMatcher.java index dece1e539..9a1e32f37 100644 --- a/client/src/main/java/io/split/engine/matchers/EqualToMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/EqualToMatcher.java @@ -2,7 +2,6 @@ import io.split.client.dtos.DataType; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/matchers/EqualToSemverMatcher.java b/client/src/main/java/io/split/engine/matchers/EqualToSemverMatcher.java new file mode 100644 index 000000000..64d9135d2 --- /dev/null +++ b/client/src/main/java/io/split/engine/matchers/EqualToSemverMatcher.java @@ -0,0 +1,54 @@ +package io.split.engine.matchers; + +import io.split.engine.evaluator.EvaluationContext; + +import java.util.Map; + +public class EqualToSemverMatcher implements Matcher { + + private final Semver _semVer; + + public EqualToSemverMatcher(String semVer) { + _semVer = Semver.build(semVer); + } + + @Override + public boolean match(Object matchValue, String bucketingKey, Map attributes, EvaluationContext evaluationContext) { + if (!(matchValue instanceof String) || _semVer == null) { + return false; + } + Semver matchSemver = Semver.build(matchValue.toString()); + if (matchSemver == null) { + return false; + } + + return matchSemver.version().equals(_semVer.version()); + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("== semver "); + bldr.append(_semVer.version()); + return bldr.toString(); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + _semVer.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof EqualToSemverMatcher)) return false; + + EqualToSemverMatcher other = (EqualToSemverMatcher) obj; + + return _semVer == other._semVer; + } + +} diff --git a/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToMatcher.java b/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToMatcher.java index 21620b920..1b83dc2c3 100644 --- a/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToMatcher.java @@ -2,7 +2,6 @@ import io.split.client.dtos.DataType; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToSemverMatcher.java b/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToSemverMatcher.java new file mode 100644 index 000000000..ffc714cca --- /dev/null +++ b/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToSemverMatcher.java @@ -0,0 +1,54 @@ +package io.split.engine.matchers; + +import io.split.engine.evaluator.EvaluationContext; + +import java.util.Map; + +public class GreaterThanOrEqualToSemverMatcher implements Matcher { + + private final Semver _semVer; + + public GreaterThanOrEqualToSemverMatcher(String semVer) { + _semVer = Semver.build(semVer); + } + + @Override + public boolean match(Object matchValue, String bucketingKey, Map attributes, EvaluationContext evaluationContext) { + if (!(matchValue instanceof String)|| _semVer == null) { + return false; + } + Semver matchSemver = Semver.build(matchValue.toString()); + if (matchSemver == null) { + return false; + } + + return matchSemver.compare(_semVer) >= 0; + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append(">= semver "); + bldr.append(_semVer.version()); + return bldr.toString(); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + _semVer.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof GreaterThanOrEqualToSemverMatcher)) return false; + + GreaterThanOrEqualToSemverMatcher other = (GreaterThanOrEqualToSemverMatcher) obj; + + return _semVer == other._semVer; + } + +} diff --git a/client/src/main/java/io/split/engine/matchers/InListSemverMatcher.java b/client/src/main/java/io/split/engine/matchers/InListSemverMatcher.java new file mode 100644 index 000000000..69fd1ea45 --- /dev/null +++ b/client/src/main/java/io/split/engine/matchers/InListSemverMatcher.java @@ -0,0 +1,77 @@ +package io.split.engine.matchers; + +import io.split.engine.evaluator.EvaluationContext; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class InListSemverMatcher implements Matcher { + + private final Set _semverlist = new HashSet<>(); + + public InListSemverMatcher(Collection whitelist) { + for (String item : whitelist) { + Semver semver = Semver.build(item); + if (semver == null) continue; + + _semverlist.add(semver); + } + } + + @Override + public boolean match(Object matchValue, String bucketingKey, Map attributes, EvaluationContext evaluationContext) { + if (!(matchValue instanceof String) || _semverlist.isEmpty()) { + return false; + } + Semver matchSemver = Semver.build(matchValue.toString()); + if (matchSemver == null) { + return false; + } + + for (Semver semverItem : _semverlist) { + if (semverItem.version().equals(matchSemver.version())) return true; + } + return false; + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("in semver list ["); + boolean first = true; + + for (Semver item : _semverlist) { + if (!first) { + bldr.append(','); + } + bldr.append('"'); + bldr.append(item.version()); + bldr.append('"'); + first = false; + } + + bldr.append("]"); + return bldr.toString(); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + _semverlist.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof InListSemverMatcher)) return false; + + InListSemverMatcher other = (InListSemverMatcher) obj; + + return _semverlist == other._semverlist; + } + +} diff --git a/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToMatcher.java b/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToMatcher.java index bd4e779f2..24a74aaba 100644 --- a/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToMatcher.java @@ -2,7 +2,6 @@ import io.split.client.dtos.DataType; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToSemverMatcher.java b/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToSemverMatcher.java new file mode 100644 index 000000000..dd05f8c4d --- /dev/null +++ b/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToSemverMatcher.java @@ -0,0 +1,54 @@ +package io.split.engine.matchers; + +import io.split.engine.evaluator.EvaluationContext; + +import java.util.Map; + +public class LessThanOrEqualToSemverMatcher implements Matcher { + + private final Semver _semVer; + + public LessThanOrEqualToSemverMatcher(String semVer) { + _semVer = Semver.build(semVer); + } + + @Override + public boolean match(Object matchValue, String bucketingKey, Map attributes, EvaluationContext evaluationContext) { + if (!(matchValue instanceof String) || _semVer == null) { + return false; + } + Semver matchSemver = Semver.build(matchValue.toString()); + if (matchSemver == null) { + return false; + } + + return matchSemver.compare(_semVer) <= 0; + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("<= semver "); + bldr.append(_semVer.version()); + return bldr.toString(); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + _semVer.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof LessThanOrEqualToSemverMatcher)) return false; + + LessThanOrEqualToSemverMatcher other = (LessThanOrEqualToSemverMatcher) obj; + + return _semVer == other._semVer; + } + +} diff --git a/client/src/main/java/io/split/engine/matchers/Semver.java b/client/src/main/java/io/split/engine/matchers/Semver.java new file mode 100644 index 000000000..7a85a0d72 --- /dev/null +++ b/client/src/main/java/io/split/engine/matchers/Semver.java @@ -0,0 +1,176 @@ +package io.split.engine.matchers; + +import io.split.client.exceptions.SemverParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +public class Semver { + private static final String METADATA_DELIMITER = "+"; + private static final String PRERELEASE_DELIMITER = "-"; + private static final String VALUE_DELIMITER = "\\."; + private static final Logger _log = LoggerFactory.getLogger(Semver.class); + + private Long _major; + private Long _minor; + private Long _patch; + private String[] _preRelease = new String[] {}; + private boolean _isStable; + private String _metadata; + private String _version; + + public static Semver build(String version) { + if (version.isEmpty()) return null; + try { + return new Semver(version); + } catch (Exception ex) { + _log.error("An error occurred during the creation of a Semver instance:", ex.getMessage()); + return null; + } + } + + public String version() { + return _version; + } + + public Long major() { + return _major; + } + + public Long minor() { + return _minor; + } + + public Long patch() { + return _patch; + } + + public String[] prerelease() { + return _preRelease; + } + + public String metadata() { + return _metadata; + } + + public boolean isStable() { + return _isStable; + } + + /** + * Precedence comparision between 2 Semver objects. + * + * @return the value {@code 0} if {@code this == toCompare}; + * a value less than {@code 0} if {@code this < toCompare}; and + * a value greater than {@code 0} if {@code this > toCompare} + */ + public int compare(Semver toCompare) { + if (_version.equals(toCompare.version())) { + return 0; + } + // Compare major, minor, and patch versions numerically + int result = Long.compare(_major, toCompare.major()); + if (result != 0) { + return result; + } + result = Long.compare(_minor, toCompare.minor()); + if (result != 0) { + return result; + } + result = Long.compare(_patch, toCompare.patch()); + if (result != 0) { + return result; + } + if (!_isStable && toCompare.isStable()) { + return -1; + } else if (_isStable && !toCompare.isStable()) { + return 1; + } + // Compare pre-release versions lexically + int minLength = Math.min(_preRelease.length, toCompare.prerelease().length); + for (int i = 0; i < minLength; i++) { + if (_preRelease[i].equals(toCompare.prerelease()[i])) { + continue; + } + if ( isNumeric(_preRelease[i]) && isNumeric(toCompare._preRelease[i])) { + return Long.compare(Integer.parseInt(_preRelease[i]), Long.parseLong(toCompare._preRelease[i])); + } + return adjustNumber(_preRelease[i].compareTo(toCompare._preRelease[i])); + } + // Compare lengths of pre-release versions + return Integer.compare(_preRelease.length, toCompare._preRelease.length); + } + + private int adjustNumber(int number) { + if (number > 0) return 1; + if (number < 0) return -1; + return 0; + } + private Semver(String version) throws SemverParseException { + String vWithoutMetadata = setAndRemoveMetadataIfExists(version); + String vWithoutPreRelease = setAndRemovePreReleaseIfExists(vWithoutMetadata); + setMajorMinorAndPatch(vWithoutPreRelease); + _version = setVersion(); + } + private String setAndRemoveMetadataIfExists(String version) throws SemverParseException { + int index = version.indexOf(METADATA_DELIMITER); + if (index == -1) { + return version; + } + _metadata = version.substring(index+1); + if (_metadata == null || _metadata.isEmpty()) { + throw new SemverParseException("Unable to convert to Semver, incorrect pre release data"); + } + return version.substring(0, index); + } + private String setAndRemovePreReleaseIfExists(String vWithoutMetadata) throws SemverParseException { + int index = vWithoutMetadata.indexOf(PRERELEASE_DELIMITER); + if (index == -1) { + _isStable = true; + return vWithoutMetadata; + } + String preReleaseData = vWithoutMetadata.substring(index+1); + _preRelease = preReleaseData.split(VALUE_DELIMITER); + if (_preRelease == null || Arrays.stream(_preRelease).allMatch(pr -> pr == null || pr.isEmpty())) { + throw new SemverParseException("Unable to convert to Semver, incorrect pre release data"); + } + return vWithoutMetadata.substring(0, index); + } + private void setMajorMinorAndPatch(String version) throws SemverParseException { + String[] vParts = version.split(VALUE_DELIMITER); + if (vParts.length != 3) + throw new SemverParseException("Unable to convert to Semver, incorrect format: " + version); + _major = Long.parseLong(vParts[0]); + _minor = Long.parseLong(vParts[1]); + _patch = Long.parseLong(vParts[2]); + } + + private String setVersion() { + String toReturn = _major + VALUE_DELIMITER + _minor + VALUE_DELIMITER + _patch; + if (_preRelease != null && _preRelease.length != 0) + { + for (int i = 0; i < _preRelease.length; i++) + { + if (isNumeric(_preRelease[i])) + { + _preRelease[i] = Long.toString(Long.parseLong(_preRelease[i])); + } + } + toReturn = toReturn + PRERELEASE_DELIMITER + String.join(VALUE_DELIMITER, _preRelease); + } + if (_metadata != null && !_metadata.isEmpty()) { + toReturn = toReturn + METADATA_DELIMITER + _metadata; + } + return toReturn; + } + + private static boolean isNumeric(String str) { + try { + Double.parseDouble(str); + return true; + } catch(NumberFormatException e){ + return false; + } + } +} diff --git a/client/src/main/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcher.java b/client/src/main/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcher.java index a2e477df7..5f4f9433a 100644 --- a/client/src/main/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.collections; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; diff --git a/client/src/main/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcher.java b/client/src/main/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcher.java index 93c7c2815..3a2514401 100644 --- a/client/src/main/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.collections; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; diff --git a/client/src/main/java/io/split/engine/matchers/collections/EqualToSetMatcher.java b/client/src/main/java/io/split/engine/matchers/collections/EqualToSetMatcher.java index bf811c70a..4a09c9efc 100644 --- a/client/src/main/java/io/split/engine/matchers/collections/EqualToSetMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/collections/EqualToSetMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.collections; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; diff --git a/client/src/main/java/io/split/engine/matchers/collections/PartOfSetMatcher.java b/client/src/main/java/io/split/engine/matchers/collections/PartOfSetMatcher.java index 88974cb58..8bb5f1399 100644 --- a/client/src/main/java/io/split/engine/matchers/collections/PartOfSetMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/collections/PartOfSetMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.collections; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; diff --git a/client/src/main/java/io/split/engine/matchers/strings/ContainsAnyOfMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/ContainsAnyOfMatcher.java index 40f950cbe..b8cbe8fca 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/ContainsAnyOfMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/ContainsAnyOfMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.strings; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; diff --git a/client/src/main/java/io/split/engine/matchers/strings/EndsWithAnyOfMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/EndsWithAnyOfMatcher.java index 971fe01cc..32ac9f7f3 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/EndsWithAnyOfMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/EndsWithAnyOfMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.strings; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; diff --git a/client/src/main/java/io/split/engine/matchers/strings/RegularExpressionMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/RegularExpressionMatcher.java index f63dbcca8..f64b3264b 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/RegularExpressionMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/RegularExpressionMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.strings; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/matchers/strings/StartsWithAnyOfMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/StartsWithAnyOfMatcher.java index bb74ee185..7f1ed2cad 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/StartsWithAnyOfMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/StartsWithAnyOfMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.strings; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; diff --git a/client/src/main/java/io/split/engine/matchers/strings/WhitelistMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/WhitelistMatcher.java index d41123e53..5068c1437 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/WhitelistMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/WhitelistMatcher.java @@ -1,7 +1,6 @@ package io.split.engine.matchers.strings; import io.split.engine.evaluator.EvaluationContext; -import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; diff --git a/client/src/main/java/io/split/engine/splitter/Splitter.java b/client/src/main/java/io/split/engine/splitter/Splitter.java index 60bb6d740..c867a81db 100644 --- a/client/src/main/java/io/split/engine/splitter/Splitter.java +++ b/client/src/main/java/io/split/engine/splitter/Splitter.java @@ -33,15 +33,15 @@ public static String getTreatment(String key, int seed, List partitio static long hash(String key, int seed, int algo) { switch (algo) { case ALGO_MURMUR: - return murmur_hash(key, seed); + return murmurHash(key, seed); case ALGO_LEGACY: default: - return legacy_hash(key, seed); + return legacyHash(key, seed); } } /*package private*/ - static long murmur_hash(String key, int seed) { + static long murmurHash(String key, int seed) { return MurmurHash3.murmurhash3_x86_32(key, 0, key.length(), seed); } @@ -56,7 +56,7 @@ public static int getBucket(String key, int seed, int algo) { } /*package private*/ - static int legacy_hash(String key, int seed) { + static int legacyHash(String key, int seed) { int h = 0; for (int i = 0; i < key.length(); i++) { h = 31 * h + key.charAt(i); diff --git a/client/src/main/java/io/split/engine/sse/AuthApiClientImp.java b/client/src/main/java/io/split/engine/sse/AuthApiClientImp.java index 9f750ada3..28464ebda 100644 --- a/client/src/main/java/io/split/engine/sse/AuthApiClientImp.java +++ b/client/src/main/java/io/split/engine/sse/AuthApiClientImp.java @@ -1,34 +1,34 @@ package io.split.engine.sse; import com.google.gson.JsonObject; +import io.split.client.dtos.SplitHttpResponse; import io.split.client.utils.Json; +import io.split.engine.common.FetchOptions; import io.split.engine.sse.dtos.AuthenticationResponse; import io.split.engine.sse.dtos.RawAuthResponse; +import io.split.service.SplitHttpClient; import io.split.telemetry.domain.enums.HTTPLatenciesEnum; import io.split.telemetry.domain.enums.LastSynchronizationRecordsEnum; import io.split.telemetry.storage.TelemetryRuntimeProducer; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.HttpStatus; -import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; -import java.nio.charset.StandardCharsets; import static com.google.common.base.Preconditions.checkNotNull; +import static io.split.Spec.SPEC_VERSION; public class AuthApiClientImp implements AuthApiClient { private static final Logger _log = LoggerFactory.getLogger(AuthApiClientImp.class); - private final CloseableHttpClient _httpClient; + private static final String SPEC = "s"; + private final SplitHttpClient _httpClient; private final String _target; private final TelemetryRuntimeProducer _telemetryRuntimeProducer; - public AuthApiClientImp(String url, CloseableHttpClient httpClient, TelemetryRuntimeProducer telemetryRuntimeProducer) { + public AuthApiClientImp(String url, SplitHttpClient httpClient, TelemetryRuntimeProducer telemetryRuntimeProducer) { _httpClient = checkNotNull(httpClient); _target = checkNotNull(url); _telemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); @@ -38,24 +38,17 @@ public AuthApiClientImp(String url, CloseableHttpClient httpClient, TelemetryRun public AuthenticationResponse Authenticate() { try { long initTime = System.currentTimeMillis(); - URI uri = new URIBuilder(_target).build(); - HttpGet request = new HttpGet(uri); - - CloseableHttpResponse response = _httpClient.execute(request); - Integer statusCode = response.getCode(); - - if (_log.isDebugEnabled()) { - _log.debug(String.format("[%s] %s. Status code: %s", request.getMethod(), uri.toURL(), statusCode)); - } + URI uri = new URIBuilder(_target).addParameter(SPEC, "" + SPEC_VERSION).build(); + SplitHttpResponse response = _httpClient.get(uri, new FetchOptions.Builder().cacheControlHeaders(false).build(), null); + Integer statusCode = response.statusCode(); if (statusCode == HttpStatus.SC_OK) { _log.debug(String.format("Success connection to: %s", _target)); - String jsonContent = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); _telemetryRuntimeProducer.recordTokenRefreshes(); _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.TOKEN, System.currentTimeMillis()); _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.TOKEN, System.currentTimeMillis()-initTime); - return getSuccessResponse(jsonContent); + return getSuccessResponse(response.body()); } _log.error(String.format("Problem to connect to : %s. Response status: %s", _target, statusCode)); diff --git a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java index 35d1c05d7..212d929f3 100644 --- a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java +++ b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java @@ -2,6 +2,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import io.split.client.RequestDecorator; import io.split.engine.sse.client.RawEvent; import io.split.engine.sse.client.SSEClient; import io.split.engine.sse.dtos.SegmentQueueDto; @@ -40,7 +41,8 @@ public class EventSourceClientImp implements EventSourceClient { PushStatusTracker pushStatusTracker, CloseableHttpClient sseHttpClient, TelemetryRuntimeProducer telemetryRuntimeProducer, - ThreadFactory threadFactory) { + ThreadFactory threadFactory, + RequestDecorator requestDecorator) { _baseStreamingUrl = checkNotNull(baseStreamingUrl); _notificationParser = checkNotNull(notificationParser); _notificationProcessor = checkNotNull(notificationProcessor); @@ -51,7 +53,8 @@ public class EventSourceClientImp implements EventSourceClient { status -> { _pushStatusTracker.handleSseStatus(status); return null; }, sseHttpClient, telemetryRuntimeProducer, - threadFactory); + threadFactory, + requestDecorator); _firstEvent = new AtomicBoolean(); } @@ -61,14 +64,16 @@ public static EventSourceClientImp build(String baseStreamingUrl, PushStatusTracker pushStatusTracker, CloseableHttpClient sseHttpClient, TelemetryRuntimeProducer telemetryRuntimeProducer, - ThreadFactory threadFactory) { + ThreadFactory threadFactory, + RequestDecorator requestDecorator) { return new EventSourceClientImp(baseStreamingUrl, new NotificationParserImp(), NotificationProcessorImp.build(featureFlagsWorker, segmentWorker, pushStatusTracker), pushStatusTracker, sseHttpClient, telemetryRuntimeProducer, - threadFactory); + threadFactory, + requestDecorator); } @Override diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java index 30dd16f20..9c2024d99 100644 --- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java +++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java @@ -1,10 +1,12 @@ package io.split.engine.sse.client; import com.google.common.base.Strings; +import io.split.client.RequestDecorator; import io.split.telemetry.domain.StreamingEvent; import io.split.telemetry.domain.enums.StreamEventsEnum; import io.split.telemetry.storage.TelemetryRuntimeProducer; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.slf4j.Logger; @@ -56,6 +58,7 @@ private enum ConnectionState { private final AtomicReference _ongoingResponse = new AtomicReference<>(); private final AtomicReference _ongoingRequest = new AtomicReference<>(); private AtomicBoolean _forcedStop; + private final RequestDecorator _requestDecorator; private final TelemetryRuntimeProducer _telemetryRuntimeProducer; @@ -63,13 +66,15 @@ public SSEClient(Function eventCallback, Function statusCallback, CloseableHttpClient client, TelemetryRuntimeProducer telemetryRuntimeProducer, - ThreadFactory threadFactory) { + ThreadFactory threadFactory, + RequestDecorator requestDecorator) { _eventCallback = eventCallback; _statusCallback = statusCallback; _client = client; _forcedStop = new AtomicBoolean(); _telemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); _connectionExecutor = buildExecutorService(threadFactory, "SPLIT-SSEConnection-%d"); + _requestDecorator = requestDecorator; } public synchronized boolean open(URI uri) { @@ -177,7 +182,9 @@ private void connectAndLoop(URI uri, CountDownLatch signal) { } private boolean establishConnection(URI uri, CountDownLatch signal) { - _ongoingRequest.set(new HttpGet(uri)); + HttpGet request = new HttpGet(uri); + request = (HttpGet) _requestDecorator.decorateHeaders(request); + _ongoingRequest.set(request); try { _ongoingResponse.set(_client.execute(_ongoingRequest.get())); if (_ongoingResponse.get().getCode() != 200) { diff --git a/client/src/main/java/io/split/engine/sse/dtos/RawAuthResponse.java b/client/src/main/java/io/split/engine/sse/dtos/RawAuthResponse.java index 4082aac72..08f21d8a4 100644 --- a/client/src/main/java/io/split/engine/sse/dtos/RawAuthResponse.java +++ b/client/src/main/java/io/split/engine/sse/dtos/RawAuthResponse.java @@ -22,7 +22,7 @@ public RawAuthResponse(boolean pushEnabled, String token) { this.pushEnabled = pushEnabled; this.token = token; - if (token != null && token != "") { + if (token != null && !token.isEmpty()) { String tokenDecoded = decodeJwt(); this.jwt = Json.fromJson(tokenDecoded, Jwt.class); } else { diff --git a/client/src/main/java/io/split/engine/sse/dtos/RawMessageNotification.java b/client/src/main/java/io/split/engine/sse/dtos/RawMessageNotification.java index f39bc8b20..9fc5ad6cd 100644 --- a/client/src/main/java/io/split/engine/sse/dtos/RawMessageNotification.java +++ b/client/src/main/java/io/split/engine/sse/dtos/RawMessageNotification.java @@ -1,7 +1,5 @@ package io.split.engine.sse.dtos; -import java.util.Map; - public class RawMessageNotification { private String id; private String clientId; diff --git a/client/src/main/java/io/split/service/HttpPostImp.java b/client/src/main/java/io/split/service/HttpPostImp.java index a98744c7c..e5baa001b 100644 --- a/client/src/main/java/io/split/service/HttpPostImp.java +++ b/client/src/main/java/io/split/service/HttpPostImp.java @@ -1,15 +1,13 @@ package io.split.service; +import io.split.client.dtos.SplitHttpResponse; import io.split.client.utils.Utils; import io.split.telemetry.domain.enums.HttpParamsWrapper; import io.split.telemetry.storage.TelemetryRuntimeProducer; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.hc.client5.http.classic.methods.HttpPost; import java.net.URI; @@ -17,10 +15,10 @@ public class HttpPostImp { private static final Logger _logger = LoggerFactory.getLogger(HttpPostImp.class); - private CloseableHttpClient _client; + private SplitHttpClient _client; private final TelemetryRuntimeProducer _telemetryRuntimeProducer; - public HttpPostImp(CloseableHttpClient client, TelemetryRuntimeProducer telemetryRuntimeProducer) { + public HttpPostImp(SplitHttpClient client, TelemetryRuntimeProducer telemetryRuntimeProducer) { _client = client; _telemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); } @@ -28,19 +26,11 @@ public HttpPostImp(CloseableHttpClient client, TelemetryRuntimeProducer telemetr public void post(URI uri, Object object, String posted, HttpParamsWrapper httpParamsWrapper) { long initTime = System.currentTimeMillis(); HttpEntity entity = Utils.toJsonEntity(object); - HttpPost request = new HttpPost(uri); - request.setEntity(entity); - if (_logger.isDebugEnabled()) { - _logger.debug(String.format("[%s] %s", request.getMethod(), uri)); - } - - try (CloseableHttpResponse response = _client.execute(request)) { - - int status = response.getCode(); - if (status < HttpStatus.SC_OK || status >= HttpStatus.SC_MULTIPLE_CHOICES) { - _telemetryRuntimeProducer.recordSyncError(httpParamsWrapper.getResourceEnum(), status); - _logger.warn(String.format("Response status was: %s. Reason: %s", status , response.getReasonPhrase())); + try { + SplitHttpResponse response = _client.post(uri, entity, null); + if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { + _telemetryRuntimeProducer.recordSyncError(httpParamsWrapper.getResourceEnum(), response.statusCode()); return; } _telemetryRuntimeProducer.recordSyncLatency(httpParamsWrapper.getHttpLatenciesEnum(), System.currentTimeMillis() - initTime); diff --git a/client/src/main/java/io/split/service/SplitHttpClient.java b/client/src/main/java/io/split/service/SplitHttpClient.java new file mode 100644 index 000000000..1c88bcd4e --- /dev/null +++ b/client/src/main/java/io/split/service/SplitHttpClient.java @@ -0,0 +1,35 @@ +package io.split.service; + +import io.split.engine.common.FetchOptions; +import io.split.client.dtos.SplitHttpResponse; + +import org.apache.hc.core5.http.HttpEntity; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +public interface SplitHttpClient extends Closeable { + /** + * Wrapper for HTTP get method + * + * @param uri the URL to be used + * @param options The FetchOptions object that contains headers. + * @return The response structure SplitHttpResponse + */ + public SplitHttpResponse get(URI uri, FetchOptions options, Map> additionalHeaders); + + /** + * Wrapper for HTTP post method + * + * @param uri the URL to be used + * @param entity HttpEntity object that has The body load + * @param additionalHeaders Any additional headers to be added. + * @return The response structure SplitHttpResponse + */ + public SplitHttpResponse post(URI uri, + HttpEntity entity, + Map> additionalHeaders) throws IOException; +} diff --git a/client/src/main/java/io/split/service/SplitHttpClientImpl.java b/client/src/main/java/io/split/service/SplitHttpClientImpl.java new file mode 100644 index 000000000..64ca3a55c --- /dev/null +++ b/client/src/main/java/io/split/service/SplitHttpClientImpl.java @@ -0,0 +1,148 @@ +package io.split.service; + +import io.split.client.RequestDecorator; +import io.split.client.utils.SDKMetadata; +import io.split.client.utils.Utils; +import io.split.engine.common.FetchOptions; +import io.split.client.dtos.SplitHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import org.apache.hc.core5.http.HttpRequest; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +public final class SplitHttpClientImpl implements SplitHttpClient { + + private static final Logger _log = LoggerFactory.getLogger(SplitHttpClient.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 CloseableHttpClient _client; + private final RequestDecorator _requestDecorator; + private final String _apikey; + private final SDKMetadata _metadata; + + public static SplitHttpClientImpl create(CloseableHttpClient client, + RequestDecorator requestDecorator, + String apikey, + SDKMetadata metadata) throws URISyntaxException { + return new SplitHttpClientImpl(client, requestDecorator, apikey, metadata); + } + + private SplitHttpClientImpl(CloseableHttpClient client, + RequestDecorator requestDecorator, + String apikey, + SDKMetadata metadata) { + _client = client; + _requestDecorator = requestDecorator; + _apikey = apikey; + _metadata = metadata; + } + + public SplitHttpResponse get(URI uri, FetchOptions options, Map> additionalHeaders) { + CloseableHttpResponse response = null; + + try { + HttpGet request = new HttpGet(uri); + setBasicHeaders(request); + if (additionalHeaders != null) { + for (Map.Entry> entry : additionalHeaders.entrySet()) { + for (String value : entry.getValue()) { + request.addHeader(entry.getKey(), value); + } + } + } + if (options.cacheControlHeadersEnabled()) { + request.setHeader(HEADER_CACHE_CONTROL_NAME, HEADER_CACHE_CONTROL_VALUE); + } + + _requestDecorator.decorateHeaders(request); + + response = _client.execute(request); + + if (_log.isDebugEnabled()) { + _log.debug(String.format("[%s] %s. Status code: %s", request.getMethod(), uri.toURL(), + response.getCode())); + } + + String statusMessage = ""; + if (response.getCode() < HttpStatus.SC_OK || response.getCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { + _log.warn(String.format("Response status was: %s. Reason: %s", response.getCode(), + response.getReasonPhrase())); + statusMessage = response.getReasonPhrase(); + } + return new SplitHttpResponse(response.getCode(), + statusMessage, + EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8), + response.getHeaders()); + } catch (Exception e) { + throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e); + } finally { + Utils.forceClose(response); + } + } + + public SplitHttpResponse post(URI uri, HttpEntity entity, Map> additionalHeaders) + throws IOException { + + CloseableHttpResponse response = null; + try { + HttpPost request = new HttpPost(uri); + setBasicHeaders(request); + if (additionalHeaders != null) { + for (Map.Entry> entry : additionalHeaders.entrySet()) { + for (String value : entry.getValue()) { + request.addHeader(entry.getKey(), value); + } + } + } + request.setEntity(entity); + request = (HttpPost) _requestDecorator.decorateHeaders(request); + + response = _client.execute(request); + + String statusMessage = ""; + if (response.getCode() < HttpStatus.SC_OK || response.getCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { + statusMessage = response.getReasonPhrase(); + _log.warn(String.format("Response status was: %s. Reason: %s", response.getCode(), + response.getReasonPhrase())); + } + return new SplitHttpResponse(response.getCode(), statusMessage, "", response.getHeaders()); + } catch (Exception e) { + throw new IOException(String.format("Problem in http post operation: %s", e), e); + } finally { + Utils.forceClose(response); + } + } + + private void setBasicHeaders(HttpRequest request) { + request.setHeader(HEADER_API_KEY, "Bearer " + _apikey); + request.setHeader(HEADER_CLIENT_VERSION, _metadata.getSdkVersion()); + request.setHeader(HEADER_CLIENT_MACHINE_IP, _metadata.getMachineIp()); + request.setHeader(HEADER_CLIENT_MACHINE_NAME, _metadata.getMachineName()); + request.setHeader(HEADER_CLIENT_KEY, _apikey.length() > 4 + ? _apikey.substring(_apikey.length() - 4) + : _apikey); + } + + @Override + public void close() throws IOException { + _client.close(); + } +} diff --git a/client/src/main/java/io/split/storages/pluggable/domain/UserPipelineWrapper.java b/client/src/main/java/io/split/storages/pluggable/domain/UserPipelineWrapper.java index 505fe11c6..81ddc691a 100644 --- a/client/src/main/java/io/split/storages/pluggable/domain/UserPipelineWrapper.java +++ b/client/src/main/java/io/split/storages/pluggable/domain/UserPipelineWrapper.java @@ -5,7 +5,6 @@ import pluggable.Pipeline; import pluggable.Result; -import java.util.ArrayList; import java.util.List; public class UserPipelineWrapper implements Pipeline{ diff --git a/client/src/main/java/io/split/telemetry/domain/HTTPErrors.java b/client/src/main/java/io/split/telemetry/domain/HTTPErrors.java index dac746117..69c85ad85 100644 --- a/client/src/main/java/io/split/telemetry/domain/HTTPErrors.java +++ b/client/src/main/java/io/split/telemetry/domain/HTTPErrors.java @@ -39,59 +39,59 @@ public HTTPErrors() { _telemetry = new ConcurrentHashMap<>(); } - public Map get_splits() { + public Map getSplits() { return _splits; } - public void set_splits(Map _splits) { + public void setSplits(Map _splits) { this._splits = _splits; } - public Map get_segments() { + public Map getSegments() { return _segments; } - public void set_segments(Map _segments) { + public void setSegments(Map _segments) { this._segments = _segments; } - public Map get_impressions() { + public Map getImpressions() { return _impressions; } - public void set_impressions(Map _impressions) { + public void setImpressions(Map _impressions) { this._impressions = _impressions; } - public Map get_events() { + public Map getEvents() { return _events; } - public void set_events(Map _events) { + public void setEvents(Map _events) { this._events = _events; } - public Map get_token() { + public Map getToken() { return _token; } - public void set_token(Map _token) { + public void setToken(Map _token) { this._token = _token; } - public Map get_telemetry() { + public Map getTelemetry() { return _telemetry; } - public void set_telemetry(Map _telemetry) { + public void setTelemetry(Map _telemetry) { this._telemetry = _telemetry; } - public Map get_impressionsCount() { + public Map getImpressionsCount() { return _impressionsCount; } - public void set_impressionsCount(Map _impressionsCount) { + public void setImpressionsCount(Map _impressionsCount) { this._impressionsCount = _impressionsCount; } -} +} \ No newline at end of file diff --git a/client/src/main/java/io/split/telemetry/domain/HTTPLatencies.java b/client/src/main/java/io/split/telemetry/domain/HTTPLatencies.java index 0e0791ed9..1b7ec2f8e 100644 --- a/client/src/main/java/io/split/telemetry/domain/HTTPLatencies.java +++ b/client/src/main/java/io/split/telemetry/domain/HTTPLatencies.java @@ -39,59 +39,59 @@ public HTTPLatencies() { _telemetry = new ArrayList<>(); } - public List get_splits() { + public List getSplits() { return _splits; } - public void set_splits(List _splits) { + public void setSplits(List _splits) { this._splits = _splits; } - public List get_segments() { + public List getSegments() { return _segments; } - public void set_segments(List _segments) { + public void setSegments(List _segments) { this._segments = _segments; } - public List get_impressions() { + public List getImpressions() { return _impressions; } - public void set_impressions(List _impressions) { + public void setImpressions(List _impressions) { this._impressions = _impressions; } - public List get_events() { + public List getEvents() { return _events; } - public void set_events(List _events) { + public void setEvents(List _events) { this._events = _events; } - public List get_token() { + public List getToken() { return _token; } - public void set_token(List _token) { + public void setToken(List _token) { this._token = _token; } - public List get_telemetry() { + public List getTelemetry() { return _telemetry; } - public void set_telemetry(List _telemetry) { + public void setTelemetry(List _telemetry) { this._telemetry = _telemetry; } - public List get_impressionsCount() { + public List getImpressionsCount() { return _impressionsCount; } - public void set_impressionsCount(List _impressionsCount) { + public void setImpressionsCount(List _impressionsCount) { this._impressionsCount = _impressionsCount; } -} +} \ No newline at end of file diff --git a/client/src/main/java/io/split/telemetry/domain/LastSynchronization.java b/client/src/main/java/io/split/telemetry/domain/LastSynchronization.java index 59586562e..74889e1f9 100644 --- a/client/src/main/java/io/split/telemetry/domain/LastSynchronization.java +++ b/client/src/main/java/io/split/telemetry/domain/LastSynchronization.java @@ -26,59 +26,59 @@ public class LastSynchronization { @SerializedName(FIELD_TELEMETRY) private long _telemetry; - public long get_splits() { + public long getSplits() { return _splits; } - public void set_splits(long _splits) { + public void setSplits(long _splits) { this._splits = _splits; } - public long get_segments() { + public long getSegments() { return _segments; } - public void set_segments(long _segments) { + public void setSegments(long _segments) { this._segments = _segments; } - public long get_impressions() { + public long getImpressions() { return _impressions; } - public void set_impressions(long _impressions) { + public void setImpressions(long _impressions) { this._impressions = _impressions; } - public long get_events() { + public long getEvents() { return _events; } - public void set_events(long _events) { + public void setEvents(long _events) { this._events = _events; } - public long get_token() { + public long getToken() { return _token; } - public void set_token(long _token) { + public void setToken(long _token) { this._token = _token; } - public long get_telemetry() { + public long getTelemetry() { return _telemetry; } - public void set_telemetry(long _telemetry) { + public void setTelemetry(long _telemetry) { this._telemetry = _telemetry; } - public long get_impressionsCount() { + public long getImpressionsCount() { return _impressionsCount; } - public void set_impressionsCount(long _impressionsCount) { + public void setImpressionsCount(long _impressionsCount) { this._impressionsCount = _impressionsCount; } -} +} \ No newline at end of file diff --git a/client/src/main/java/io/split/telemetry/domain/Rates.java b/client/src/main/java/io/split/telemetry/domain/Rates.java index e80d26079..0ae5efdac 100644 --- a/client/src/main/java/io/split/telemetry/domain/Rates.java +++ b/client/src/main/java/io/split/telemetry/domain/Rates.java @@ -20,43 +20,43 @@ public class Rates { @SerializedName(FIELD_TELEMETRY) private long _telemetry; - public long get_splits() { + public long getSplits() { return _splits; } - public void set_splits(long _splits) { + public void setSplits(long _splits) { this._splits = _splits; } - public long get_segments() { + public long getSegments() { return _segments; } - public void set_segments(long _segments) { + public void setSegments(long _segments) { this._segments = _segments; } - public long get_impressions() { + public long getImpressions() { return _impressions; } - public void set_impressions(long _impressions) { + public void setImpressions(long _impressions) { this._impressions = _impressions; } - public long get_events() { + public long getEvents() { return _events; } - public void set_events(long _events) { + public void setEvents(long _events) { this._events = _events; } - public long get_telemetry() { + public long getTelemetry() { return _telemetry; } - public void set_telemetry(long _telemetry) { + public void setTelemetry(long _telemetry) { this._telemetry = _telemetry; } -} +} \ No newline at end of file diff --git a/client/src/main/java/io/split/telemetry/domain/Stats.java b/client/src/main/java/io/split/telemetry/domain/Stats.java index 7d271969f..55882cdc9 100644 --- a/client/src/main/java/io/split/telemetry/domain/Stats.java +++ b/client/src/main/java/io/split/telemetry/domain/Stats.java @@ -215,4 +215,4 @@ public UpdatesFromSSE getUpdatesFromSSE() { public void setUpdatesFromSSE(UpdatesFromSSE updatesFromSSE) { this._updatesFromSSE = updatesFromSSE; } -} +} \ No newline at end of file diff --git a/client/src/main/java/io/split/telemetry/domain/StreamingEvent.java b/client/src/main/java/io/split/telemetry/domain/StreamingEvent.java index 161b29762..9fd3ae568 100644 --- a/client/src/main/java/io/split/telemetry/domain/StreamingEvent.java +++ b/client/src/main/java/io/split/telemetry/domain/StreamingEvent.java @@ -20,19 +20,19 @@ public StreamingEvent(int _type, long _data, long _timestamp) { this._timestamp = _timestamp; } - public int get_type() { + public int getType() { return _type; } - public void set_type(int _type) { + public void setType(int _type) { this._type = _type; } - public long get_data() { + public long getData() { return _data; } - public void set_data(long _data) { + public void setData(long _data) { this._data = _data; } @@ -43,4 +43,4 @@ public long getTimestamp() { public void setTimestamp(long timestamp) { this._timestamp = timestamp; } -} +} \ No newline at end of file diff --git a/client/src/main/java/io/split/telemetry/domain/URLOverrides.java b/client/src/main/java/io/split/telemetry/domain/URLOverrides.java index 5813f1d6c..587fdf3dd 100644 --- a/client/src/main/java/io/split/telemetry/domain/URLOverrides.java +++ b/client/src/main/java/io/split/telemetry/domain/URLOverrides.java @@ -20,43 +20,43 @@ public class URLOverrides { @SerializedName(FIELD_TELEMETRY) private boolean _telemetry; - public boolean is_sdk() { + public boolean isSdk() { return _sdk; } - public void set_sdk(boolean _sdk) { + public void setSdk(boolean _sdk) { this._sdk = _sdk; } - public boolean is_events() { + public boolean isEvents() { return _events; } - public void set_events(boolean _events) { + public void setEvents(boolean _events) { this._events = _events; } - public boolean is_auth() { + public boolean isAuth() { return _auth; } - public void set_auth(boolean _auth) { + public void setAuth(boolean _auth) { this._auth = _auth; } - public boolean is_stream() { + public boolean isStream() { return _stream; } - public void set_stream(boolean _stream) { + public void setStream(boolean _stream) { this._stream = _stream; } - public boolean is_telemetry() { + public boolean isTelemetry() { return _telemetry; } - public void set_telemetry(boolean _telemetry) { + public void setTelemetry(boolean _telemetry) { this._telemetry = _telemetry; } -} +} \ No newline at end of file diff --git a/client/src/main/java/io/split/telemetry/storage/InMemoryTelemetryStorage.java b/client/src/main/java/io/split/telemetry/storage/InMemoryTelemetryStorage.java index 94246fa7f..6958f110a 100644 --- a/client/src/main/java/io/split/telemetry/storage/InMemoryTelemetryStorage.java +++ b/client/src/main/java/io/split/telemetry/storage/InMemoryTelemetryStorage.java @@ -152,13 +152,13 @@ public long getEventStats(EventsDataRecordsEnum dataType) { @Override public LastSynchronization getLastSynchronization() { LastSynchronization lastSynchronization = new LastSynchronization(); - lastSynchronization.set_splits(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.SPLITS).get()); - lastSynchronization.set_segments(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.SEGMENTS).get()); - lastSynchronization.set_impressions(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.IMPRESSIONS).get()); - lastSynchronization.set_impressionsCount(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.IMPRESSIONS_COUNT).get()); - lastSynchronization.set_events(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.EVENTS).get()); - lastSynchronization.set_telemetry(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.TELEMETRY).get()); - lastSynchronization.set_token(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.TOKEN).get()); + lastSynchronization.setSplits(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.SPLITS).get()); + lastSynchronization.setSegments(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.SEGMENTS).get()); + lastSynchronization.setImpressions(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.IMPRESSIONS).get()); + lastSynchronization.setImpressionsCount(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.IMPRESSIONS_COUNT).get()); + lastSynchronization.setEvents(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.EVENTS).get()); + lastSynchronization.setTelemetry(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.TELEMETRY).get()); + lastSynchronization.setToken(_lastSynchronizationRecords.get(LastSynchronizationRecordsEnum.TOKEN).get()); return lastSynchronization; } @@ -166,13 +166,13 @@ public LastSynchronization getLastSynchronization() { @Override public HTTPErrors popHTTPErrors() { HTTPErrors errors = new HTTPErrors(); - errors.set_splits(_httpErrors.get(ResourceEnum.SPLIT_SYNC)); - errors.set_segments(_httpErrors.get(ResourceEnum.SEGMENT_SYNC)); - errors.set_impressions(_httpErrors.get(ResourceEnum.IMPRESSION_SYNC)); - errors.set_impressionsCount(_httpErrors.get(ResourceEnum.IMPRESSION_COUNT_SYNC)); - errors.set_events(_httpErrors.get(ResourceEnum.EVENT_SYNC)); - errors.set_telemetry(_httpErrors.get(ResourceEnum.TELEMETRY_SYNC)); - errors.set_token(_httpErrors.get(ResourceEnum.TOKEN_SYNC)); + errors.setSplits(_httpErrors.get(ResourceEnum.SPLIT_SYNC)); + errors.setSegments(_httpErrors.get(ResourceEnum.SEGMENT_SYNC)); + errors.setImpressions(_httpErrors.get(ResourceEnum.IMPRESSION_SYNC)); + errors.setImpressionsCount(_httpErrors.get(ResourceEnum.IMPRESSION_COUNT_SYNC)); + errors.setEvents(_httpErrors.get(ResourceEnum.EVENT_SYNC)); + errors.setTelemetry(_httpErrors.get(ResourceEnum.TELEMETRY_SYNC)); + errors.setToken(_httpErrors.get(ResourceEnum.TOKEN_SYNC)); _httpErrors.clear(); initHttpErrors(); @@ -183,13 +183,13 @@ public HTTPErrors popHTTPErrors() { @Override public HTTPLatencies popHTTPLatencies(){ HTTPLatencies latencies = new HTTPLatencies(); - latencies.set_splits(_httpLatencies.get(HTTPLatenciesEnum.SPLITS).fetchAndClearAll()); - latencies.set_segments(_httpLatencies.get(HTTPLatenciesEnum.SEGMENTS).fetchAndClearAll()); - latencies.set_impressions(_httpLatencies.get(HTTPLatenciesEnum.IMPRESSIONS).fetchAndClearAll()); - latencies.set_impressionsCount(_httpLatencies.get(HTTPLatenciesEnum.IMPRESSIONS_COUNT).fetchAndClearAll()); - latencies.set_events(_httpLatencies.get(HTTPLatenciesEnum.EVENTS).fetchAndClearAll()); - latencies.set_telemetry(_httpLatencies.get(HTTPLatenciesEnum.TELEMETRY).fetchAndClearAll()); - latencies.set_token(_httpLatencies.get(HTTPLatenciesEnum.TOKEN).fetchAndClearAll()); + latencies.setSplits(_httpLatencies.get(HTTPLatenciesEnum.SPLITS).fetchAndClearAll()); + latencies.setSegments(_httpLatencies.get(HTTPLatenciesEnum.SEGMENTS).fetchAndClearAll()); + latencies.setImpressions(_httpLatencies.get(HTTPLatenciesEnum.IMPRESSIONS).fetchAndClearAll()); + latencies.setImpressionsCount(_httpLatencies.get(HTTPLatenciesEnum.IMPRESSIONS_COUNT).fetchAndClearAll()); + latencies.setEvents(_httpLatencies.get(HTTPLatenciesEnum.EVENTS).fetchAndClearAll()); + latencies.setTelemetry(_httpLatencies.get(HTTPLatenciesEnum.TELEMETRY).fetchAndClearAll()); + latencies.setToken(_httpLatencies.get(HTTPLatenciesEnum.TOKEN).fetchAndClearAll()); return latencies; } diff --git a/client/src/main/java/io/split/telemetry/synchronizer/HttpTelemetryMemorySender.java b/client/src/main/java/io/split/telemetry/synchronizer/HttpTelemetryMemorySender.java index 1cfd39fb3..4388eecaa 100644 --- a/client/src/main/java/io/split/telemetry/synchronizer/HttpTelemetryMemorySender.java +++ b/client/src/main/java/io/split/telemetry/synchronizer/HttpTelemetryMemorySender.java @@ -4,11 +4,11 @@ import io.split.client.dtos.UniqueKeys; import io.split.client.utils.Utils; import io.split.service.HttpPostImp; +import io.split.service.SplitHttpClient; import io.split.telemetry.domain.Config; import io.split.telemetry.domain.Stats; import io.split.telemetry.domain.enums.HttpParamsWrapper; import io.split.telemetry.storage.TelemetryRuntimeProducer; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +31,7 @@ public class HttpTelemetryMemorySender{ private final URI _uniqueKeysTarget; private final HttpPostImp _httpPost; - public static HttpTelemetryMemorySender create(CloseableHttpClient client, URI telemetryRootEndpoint, + public static HttpTelemetryMemorySender create(SplitHttpClient client, URI telemetryRootEndpoint, TelemetryRuntimeProducer telemetryRuntimeProducer) throws URISyntaxException { return new HttpTelemetryMemorySender(client, Utils.appendPath(telemetryRootEndpoint,CONFIG_ENDPOINT_PATH), @@ -42,7 +42,7 @@ public static HttpTelemetryMemorySender create(CloseableHttpClient client, URI t } @VisibleForTesting - HttpTelemetryMemorySender(CloseableHttpClient client, URI impressionConfigTarget, URI impressionStatsTarget, + HttpTelemetryMemorySender(SplitHttpClient client, URI impressionConfigTarget, URI impressionStatsTarget, URI uniqueKeysTarget,TelemetryRuntimeProducer telemetryRuntimeProducer) { _httpPost = new HttpPostImp(client, telemetryRuntimeProducer); _impressionConfigTarget = impressionConfigTarget; diff --git a/client/src/main/java/io/split/telemetry/synchronizer/TelemetryInMemorySubmitter.java b/client/src/main/java/io/split/telemetry/synchronizer/TelemetryInMemorySubmitter.java index 4f52bcdf1..5559332a1 100644 --- a/client/src/main/java/io/split/telemetry/synchronizer/TelemetryInMemorySubmitter.java +++ b/client/src/main/java/io/split/telemetry/synchronizer/TelemetryInMemorySubmitter.java @@ -7,6 +7,7 @@ import io.split.client.impressions.ImpressionsManager; import io.split.integrations.IntegrationsConfig; import io.split.integrations.NewRelicListener; +import io.split.service.SplitHttpClient; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SplitCacheConsumer; import io.split.telemetry.domain.Config; @@ -17,7 +18,6 @@ import io.split.telemetry.domain.enums.ImpressionsDataTypeEnum; import io.split.telemetry.storage.TelemetryRuntimeProducer; import io.split.telemetry.storage.TelemetryStorageConsumer; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import java.net.URI; import java.net.URISyntaxException; @@ -38,7 +38,7 @@ public class TelemetryInMemorySubmitter implements TelemetrySynchronizer{ private SegmentCacheConsumer _segmentCacheConsumer; private final long _initStartTime; - public TelemetryInMemorySubmitter(CloseableHttpClient client, URI telemetryRootEndpoint, TelemetryStorageConsumer telemetryStorageConsumer, + public TelemetryInMemorySubmitter(SplitHttpClient client, URI telemetryRootEndpoint, TelemetryStorageConsumer telemetryStorageConsumer, SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCacheConsumer, TelemetryRuntimeProducer telemetryRuntimeProducer, long initStartTime) throws URISyntaxException { _httpHttpTelemetryMemorySender = HttpTelemetryMemorySender.create(client, telemetryRootEndpoint, telemetryRuntimeProducer); @@ -109,17 +109,17 @@ Config generateConfig(SplitClientConfig splitClientConfig, long readyTimestamp, } List impressions = getImpressions(impressionsListeners); - rates.set_telemetry(splitClientConfig.get_telemetryRefreshRate()); - rates.set_events(splitClientConfig.eventSendIntervalInMillis()); - rates.set_impressions(splitClientConfig.impressionsRefreshRate()); - rates.set_segments(splitClientConfig.segmentsRefreshRate()); - rates.set_splits(splitClientConfig.featuresRefreshRate()); + rates.setTelemetry(splitClientConfig.getTelemetryRefreshRate()); + rates.setEvents(splitClientConfig.eventSendIntervalInMillis()); + rates.setImpressions(splitClientConfig.impressionsRefreshRate()); + rates.setSegments(splitClientConfig.segmentsRefreshRate()); + rates.setSplits(splitClientConfig.featuresRefreshRate()); - urlOverrides.set_auth(!SplitClientConfig.AUTH_ENDPOINT.equals(splitClientConfig.authServiceURL())); - urlOverrides.set_stream(!SplitClientConfig.STREAMING_ENDPOINT.equals(splitClientConfig.streamingServiceURL())); - urlOverrides.set_sdk(!SplitClientConfig.SDK_ENDPOINT.equals(splitClientConfig.endpoint())); - urlOverrides.set_events(!SplitClientConfig.EVENTS_ENDPOINT.equals(splitClientConfig.eventsEndpoint())); - urlOverrides.set_telemetry(!SplitClientConfig.TELEMETRY_ENDPOINT.equals(splitClientConfig.telemetryURL())); + urlOverrides.setAuth(!SplitClientConfig.AUTH_ENDPOINT.equals(splitClientConfig.authServiceURL())); + urlOverrides.setStream(!SplitClientConfig.STREAMING_ENDPOINT.equals(splitClientConfig.streamingServiceURL())); + urlOverrides.setSdk(!SplitClientConfig.SDK_ENDPOINT.equals(splitClientConfig.endpoint())); + urlOverrides.setEvents(!SplitClientConfig.EVENTS_ENDPOINT.equals(splitClientConfig.eventsEndpoint())); + urlOverrides.setTelemetry(!SplitClientConfig.TELEMETRY_ENDPOINT.equals(splitClientConfig.telemetryURL())); config.setBurTimeouts(_telemetryStorageConsumer.getBURTimeouts()); config.setNonReadyUsages(_telemetryStorageConsumer.getNonReadyUsages()); @@ -170,4 +170,4 @@ private List getImpressions(List requestCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class); CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class); - when(httpClientMock.execute(requestCaptor.capture())).thenReturn(TestHelper.classicResponseToCloseableMock(response)); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + + when(httpClientMock.execute(requestCaptor.capture())) + .thenReturn(TestHelper.classicResponseToCloseableMock(response)); Metrics.NoopMetrics metrics = new Metrics.NoopMetrics(); - HttpSegmentChangeFetcher fetcher = HttpSegmentChangeFetcher.create(httpClientMock, rootTarget, Mockito.mock(TelemetryStorage.class)); + HttpSegmentChangeFetcher fetcher = HttpSegmentChangeFetcher.create(splitHtpClient, rootTarget, + Mockito.mock(TelemetryStorage.class)); fetcher.fetch("someSegment", -1, new FetchOptions.Builder().targetChangeNumber(123).build()); - fetcher.fetch("someSegment2",-1, new FetchOptions.Builder().build()); + fetcher.fetch("someSegment2", -1, new FetchOptions.Builder().build()); List captured = requestCaptor.getAllValues(); Assert.assertEquals(captured.size(), 2); Assert.assertTrue(captured.get(0).getUri().toString().contains("till=123")); Assert.assertFalse(captured.get(1).getUri().toString().contains("till=")); } + @Test(expected = IllegalStateException.class) + public void testFetcherWithError() throws IOException, URISyntaxException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException { + URI rootTarget = URI.create("https://api.split.io"); + + HttpEntity entityMock = Mockito.mock(HttpEntity.class); + when(entityMock.getContent()).thenReturn(new StringBufferInputStream("{\"till\": 1}")); + ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class); + when(response.getCode()).thenReturn(400); + when(response.getEntity()).thenReturn(entityMock); + when(response.getHeaders()).thenReturn(new Header[0]); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class); + CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + + when(httpClientMock.execute(requestCaptor.capture())) + .thenReturn(TestHelper.classicResponseToCloseableMock(response)); + + Metrics.NoopMetrics metrics = new Metrics.NoopMetrics(); + HttpSegmentChangeFetcher fetcher = HttpSegmentChangeFetcher.create(splitHtpClient, rootTarget, + Mockito.mock(TelemetryStorage.class)); + + fetcher.fetch("someSegment", -1, new FetchOptions.Builder().build()); + } + + private SDKMetadata metadata() { + return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); + } } diff --git a/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java b/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java index 1784c44ed..8d18f8456 100644 --- a/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java @@ -3,8 +3,11 @@ import io.split.TestHelper; import io.split.client.dtos.Split; import io.split.client.dtos.SplitChange; +import io.split.client.utils.SDKMetadata; import io.split.engine.common.FetchOptions; import io.split.engine.metrics.Metrics; +import io.split.service.SplitHttpClient; +import io.split.service.SplitHttpClientImpl; import io.split.telemetry.storage.InMemoryTelemetryStorage; import io.split.telemetry.storage.TelemetryRuntimeProducer; import io.split.telemetry.storage.TelemetryStorage; @@ -26,21 +29,24 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.sql.Array; +import java.util.*; +import java.util.stream.Collectors; import static org.mockito.Mockito.when; public class HttpSplitChangeFetcherTest { private static final TelemetryStorage TELEMETRY_STORAGE = Mockito.mock(InMemoryTelemetryStorage.class); + @Test public void testDefaultURL() throws URISyntaxException { URI rootTarget = URI.create("https://api.split.io"); CloseableHttpClient httpClient = HttpClients.custom().build(); Metrics.NoopMetrics metrics = new Metrics.NoopMetrics(); - HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClient, rootTarget, TELEMETRY_STORAGE); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, TELEMETRY_STORAGE); Assert.assertEquals("https://api.split.io/api/splitChanges", fetcher.getTarget().toString()); } @@ -48,7 +54,10 @@ public void testDefaultURL() throws URISyntaxException { public void testCustomURLNoPathNoBackslash() throws URISyntaxException { URI rootTarget = URI.create("https://kubernetesturl.com/split"); CloseableHttpClient httpClient = HttpClients.custom().build(); - HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClient, rootTarget, TELEMETRY_STORAGE); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, TELEMETRY_STORAGE); Assert.assertEquals("https://kubernetesturl.com/split/api/splitChanges", fetcher.getTarget().toString()); } @@ -56,7 +65,9 @@ public void testCustomURLNoPathNoBackslash() throws URISyntaxException { public void testCustomURLAppendingPath() throws URISyntaxException { URI rootTarget = URI.create("https://kubernetesturl.com/split/"); CloseableHttpClient httpClient = HttpClients.custom().build(); - HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClient, rootTarget, TELEMETRY_STORAGE); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, TELEMETRY_STORAGE); Assert.assertEquals("https://kubernetesturl.com/split/api/splitChanges", fetcher.getTarget().toString()); } @@ -64,17 +75,24 @@ public void testCustomURLAppendingPath() throws URISyntaxException { public void testCustomURLAppendingPathNoBackslash() throws URISyntaxException { URI rootTarget = URI.create("https://kubernetesturl.com/split"); CloseableHttpClient httpClient = HttpClients.custom().build(); - HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClient, rootTarget, TELEMETRY_STORAGE); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, TELEMETRY_STORAGE); Assert.assertEquals("https://kubernetesturl.com/split/api/splitChanges", fetcher.getTarget().toString()); } @Test - public void testFetcherWithSpecialCharacters() throws URISyntaxException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException { + public void testFetcherWithSpecialCharacters() throws URISyntaxException, InvocationTargetException, + NoSuchMethodException, IllegalAccessException, IOException { URI rootTarget = URI.create("https://api.split.io"); - CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("split-change-special-characters.json", HttpStatus.SC_OK); + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("split-change-special-characters.json", + HttpStatus.SC_OK); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", + metadata()); - HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClientMock, rootTarget, TELEMETRY_STORAGE); + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, TELEMETRY_STORAGE); SplitChange change = fetcher.fetch(1234567, new FetchOptions.Builder().cacheControlHeaders(true).build()); @@ -91,11 +109,13 @@ public void testFetcherWithSpecialCharacters() throws URISyntaxException, Invoca } @Test - public void testFetcherWithCDNBypassOption() throws IOException, URISyntaxException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + public void testFetcherWithCDNBypassOption() throws IOException, URISyntaxException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException { URI rootTarget = URI.create("https://api.split.io"); HttpEntity entityMock = Mockito.mock(HttpEntity.class); - when(entityMock.getContent()).thenReturn(new ByteArrayInputStream("{\"till\": 1}".getBytes(StandardCharsets.UTF_8))); + when(entityMock.getContent()) + .thenReturn(new ByteArrayInputStream("{\"till\": 1}".getBytes(StandardCharsets.UTF_8))); ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class); when(response.getCode()).thenReturn(200); when(response.getEntity()).thenReturn(entityMock); @@ -103,9 +123,13 @@ public void testFetcherWithCDNBypassOption() throws IOException, URISyntaxExcept ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class); CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class); - when(httpClientMock.execute(requestCaptor.capture())).thenReturn(TestHelper.classicResponseToCloseableMock(response)); + when(httpClientMock.execute(requestCaptor.capture())) + .thenReturn(TestHelper.classicResponseToCloseableMock(response)); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); - HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClientMock, rootTarget, Mockito.mock(TelemetryRuntimeProducer.class)); + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, + Mockito.mock(TelemetryRuntimeProducer.class)); fetcher.fetch(-1, new FetchOptions.Builder().targetChangeNumber(123).build()); fetcher.fetch(-1, new FetchOptions.Builder().build()); @@ -119,10 +143,13 @@ public void testFetcherWithCDNBypassOption() throws IOException, URISyntaxExcept public void testRandomNumberGeneration() throws URISyntaxException { URI rootTarget = URI.create("https://api.split.io"); CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class); - HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClientMock, rootTarget, Mockito.mock(TelemetryRuntimeProducer.class)); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, + Mockito.mock(TelemetryRuntimeProducer.class)); Set seen = new HashSet<>(); - long min = (long)Math.pow(2, 63) * (-1); + long min = (long) Math.pow(2, 63) * (-1); final long total = 10000000; for (long x = 0; x < total; x++) { long r = fetcher.makeRandomTill(); @@ -132,4 +159,40 @@ public void testRandomNumberGeneration() throws URISyntaxException { Assert.assertTrue(seen.size() >= (total * 0.9999)); } -} \ No newline at end of file + + @Test(expected = IllegalStateException.class) + public void testURLTooLong() throws IOException, URISyntaxException, IllegalAccessException, NoSuchMethodException, + InvocationTargetException { + URI rootTarget = URI.create("https://api.split.io"); + + HttpEntity entityMock = Mockito.mock(HttpEntity.class); + when(entityMock.getContent()) + .thenReturn(new ByteArrayInputStream("{\"till\": 1}".getBytes(StandardCharsets.UTF_8))); + ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class); + when(response.getCode()).thenReturn(414); + when(response.getEntity()).thenReturn(entityMock); + when(response.getHeaders()).thenReturn(new Header[0]); + CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class); + when(httpClientMock.execute(requestCaptor.capture())) + .thenReturn(TestHelper.classicResponseToCloseableMock(response)); + + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, + Mockito.mock(TelemetryRuntimeProducer.class)); + List sets = new ArrayList(); + for (Integer i = 0; i < 100; i++) { + sets.add("set" + i.toString()); + } + String result = sets.stream() + .map(n -> String.valueOf(n)) + .collect(Collectors.joining(",", "", "")); + fetcher.fetch(-1, new FetchOptions.Builder().flagSetsFilter(result).cacheControlHeaders(false).build()); + } + + private SDKMetadata metadata() { + return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); + } + +} diff --git a/client/src/test/java/io/split/client/RequestDecoratorTest.java b/client/src/test/java/io/split/client/RequestDecoratorTest.java new file mode 100644 index 000000000..62868eb40 --- /dev/null +++ b/client/src/test/java/io/split/client/RequestDecoratorTest.java @@ -0,0 +1,111 @@ +package io.split.client; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.ProtocolException; +import org.junit.Assert; +import org.junit.Test; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +import io.split.client.dtos.RequestContext; + +import java.util.List; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class RequestDecoratorTest { + + @Test + public void testNoOp() { + RequestDecorator decorator = new RequestDecorator(null); + HttpGet request = new HttpGet("http://anyhost"); + request = (HttpGet) decorator.decorateHeaders(request); + Assert.assertEquals(0, request.getHeaders().length); + request.addHeader("myheader", "value"); + request = (HttpGet) decorator.decorateHeaders(request); + Assert.assertEquals(1, request.getHeaders().length); + } + + @Test + public void testAddCustomHeaders() throws ProtocolException { + class MyCustomHeaders implements CustomHeaderDecorator { + public MyCustomHeaders() {} + @Override + public Map> getHeaderOverrides(RequestContext context) { + Map> additionalHeaders = context.headers(); + additionalHeaders.put("first", Arrays.asList("1")); + additionalHeaders.put("second", Arrays.asList("2.1", "2.2")); + additionalHeaders.put("third", Arrays.asList("3")); + return additionalHeaders; + } + } + MyCustomHeaders myHeaders = new MyCustomHeaders(); + RequestDecorator decorator = new RequestDecorator(myHeaders); + HttpGet request = new HttpGet("http://anyhost"); + request.addHeader("first", "myfirstheader"); + request = (HttpGet) decorator.decorateHeaders(request); + + Assert.assertEquals(4, request.getHeaders().length); + Assert.assertEquals("1", request.getHeader("first").getValue()); + + Header[] second = request.getHeaders("second"); + Assert.assertEquals("2.1", second[0].getValue()); + Assert.assertEquals("2.2", second[1].getValue()); + Assert.assertEquals("3", request.getHeader("third").getValue()); + + HttpPost request2 = new HttpPost("http://anyhost"); + request2.addHeader("myheader", "value"); + request2 = (HttpPost) decorator.decorateHeaders(request2); + Assert.assertEquals(5, request2.getHeaders().length); + } + + @Test + public void testAddBlockedHeaders() throws ProtocolException { + class MyCustomHeaders implements CustomHeaderDecorator { + public MyCustomHeaders() {} + @Override + public Map> getHeaderOverrides(RequestContext context) { + Map> additionalHeaders = context.headers(); + additionalHeaders.put("first", Arrays.asList("1")); + additionalHeaders.put("SplitSDKVersion", Arrays.asList("2.4")); + additionalHeaders.put("SplitMachineip", Arrays.asList("xx")); + additionalHeaders.put("splitMachineName", Arrays.asList("xx")); + additionalHeaders.put("splitimpressionsmode", Arrays.asList("xx")); + additionalHeaders.put("HOST", Arrays.asList("xx")); + additionalHeaders.put("referrer", Arrays.asList("xx")); + additionalHeaders.put("content-type", Arrays.asList("xx")); + additionalHeaders.put("content-length", Arrays.asList("xx")); + additionalHeaders.put("content-encoding", Arrays.asList("xx")); + additionalHeaders.put("ACCEPT", Arrays.asList("xx")); + additionalHeaders.put("keep-alive", Arrays.asList("xx")); + additionalHeaders.put("x-fastly-debug", Arrays.asList("xx")); + return additionalHeaders; + } + } + MyCustomHeaders myHeaders = new MyCustomHeaders(); + RequestDecorator decorator = new RequestDecorator(myHeaders); + HttpGet request = new HttpGet("http://anyhost"); + request = (HttpGet) decorator.decorateHeaders(request); + Assert.assertEquals(1, request.getHeaders().length); + Assert.assertEquals(null, request.getHeader("SplitSDKVersion")); + } + + @Test(expected = IllegalArgumentException.class) + public void customDecoratorError() { + class MyCustomHeaders implements CustomHeaderDecorator { + public MyCustomHeaders() {} + @Override + public Map> getHeaderOverrides(RequestContext context) { + throw new RuntimeException(); + } + } + MyCustomHeaders myHeaders = new MyCustomHeaders(); + RequestDecorator decorator = new RequestDecorator(myHeaders); + HttpGet request = new HttpGet("http://anyhost"); + request = (HttpGet) decorator.decorateHeaders(request); + } +} \ No newline at end of file diff --git a/client/src/test/java/io/split/client/SplitClientConfigTest.java b/client/src/test/java/io/split/client/SplitClientConfigTest.java index 8e87ce938..1b640071c 100644 --- a/client/src/test/java/io/split/client/SplitClientConfigTest.java +++ b/client/src/test/java/io/split/client/SplitClientConfigTest.java @@ -4,12 +4,14 @@ import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionListener; import io.split.client.impressions.ImpressionsManager; +import io.split.client.dtos.RequestContext; import io.split.integrations.IntegrationsConfig; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -235,4 +237,21 @@ public void IntegrationConfigAsyncNotNull() { Assert.assertEquals(0, config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.SYNC).size()); Assert.assertEquals(1, config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.ASYNC).size()); } + + @Test + public void checkUserCustomdHeaderDecorator() { + CustomHeaderDecorator ucd = new CustomHeaderDecorator() { + @Override + public Map> getHeaderOverrides(RequestContext context) { + return null; + } + }; + SplitClientConfig config = SplitClientConfig.builder().customHeaderDecorator(ucd).build(); + Assert.assertNotNull(config.customHeaderDecorator()); + Assert.assertEquals(ucd, config.customHeaderDecorator()); + + SplitClientConfig config2 = SplitClientConfig.builder().build(); + Assert.assertNull(config2.customHeaderDecorator()); + + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/client/SplitManagerImplTest.java b/client/src/test/java/io/split/client/SplitManagerImplTest.java index 1a67a5822..d4a5c6a87 100644 --- a/client/src/test/java/io/split/client/SplitManagerImplTest.java +++ b/client/src/test/java/io/split/client/SplitManagerImplTest.java @@ -164,7 +164,7 @@ public void splitNamesCallWithSplit() { } @Test - public void block_until_ready_does_not_time_when_sdk_is_ready() throws TimeoutException, InterruptedException { + public void blockUntilReadyDoesNotTimeWhenSdkIsReady() throws TimeoutException, InterruptedException { SDKReadinessGates ready = mock(SDKReadinessGates.class); when(ready.waitUntilInternalReady(100)).thenReturn(true); SplitManagerImpl splitManager = new SplitManagerImpl(mock(SplitCacheConsumer.class), @@ -175,7 +175,7 @@ public void block_until_ready_does_not_time_when_sdk_is_ready() throws TimeoutEx } @Test(expected = TimeoutException.class) - public void block_until_ready_times_when_sdk_is_not_ready() throws TimeoutException, InterruptedException { + public void blockUntilReadyTimesWhenSdkIsNotReady() throws TimeoutException, InterruptedException { SDKReadinessGates ready = mock(SDKReadinessGates.class); when(ready.waitUntilInternalReady(100)).thenReturn(false); diff --git a/client/src/test/java/io/split/client/dtos/ImpressionCountTest.java b/client/src/test/java/io/split/client/dtos/ImpressionCountTest.java index ee154c29b..140a4b30e 100644 --- a/client/src/test/java/io/split/client/dtos/ImpressionCountTest.java +++ b/client/src/test/java/io/split/client/dtos/ImpressionCountTest.java @@ -9,7 +9,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; diff --git a/client/src/test/java/io/split/client/events/EventsSenderTest.java b/client/src/test/java/io/split/client/events/EventsSenderTest.java index 73ed904db..c9ddb754b 100644 --- a/client/src/test/java/io/split/client/events/EventsSenderTest.java +++ b/client/src/test/java/io/split/client/events/EventsSenderTest.java @@ -1,44 +1,79 @@ package io.split.client.events; +import io.split.TestHelper; +import io.split.client.RequestDecorator; +import io.split.client.utils.SDKMetadata; +import io.split.service.SplitHttpClient; +import io.split.service.SplitHttpClientImpl; import io.split.telemetry.storage.TelemetryRuntimeProducer; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; +import java.util.*; public class EventsSenderTest { - private static final TelemetryRuntimeProducer TELEMETRY_RUNTIME_CONSUMER = Mockito.mock(TelemetryRuntimeProducer.class); + private static final TelemetryRuntimeProducer TELEMETRY_RUNTIME_CONSUMER = Mockito + .mock(TelemetryRuntimeProducer.class); private static final CloseableHttpClient CLOSEABLE_HTTP_CLIENT = Mockito.mock(CloseableHttpClient.class); @Test public void testDefaultURL() throws URISyntaxException { + SplitHttpClient SPLIT_HTTP_CLIENT = SplitHttpClientImpl.create(CLOSEABLE_HTTP_CLIENT, + new RequestDecorator(null), "qwerty", metadata()); URI rootTarget = URI.create("https://api.split.io"); - EventsSender fetcher = EventsSender.create(CLOSEABLE_HTTP_CLIENT, rootTarget, TELEMETRY_RUNTIME_CONSUMER); + EventsSender fetcher = EventsSender.create(SPLIT_HTTP_CLIENT, rootTarget, TELEMETRY_RUNTIME_CONSUMER); Assert.assertEquals("https://api.split.io/api/events/bulk", fetcher.getBulkEndpoint().toString()); } @Test public void testCustomURLNoPathNoBackslash() throws URISyntaxException { + SplitHttpClient SPLIT_HTTP_CLIENT = SplitHttpClientImpl.create(CLOSEABLE_HTTP_CLIENT, + new RequestDecorator(null), "qwerty", metadata()); URI rootTarget = URI.create("https://kubernetesturl.com"); - EventsSender fetcher = EventsSender.create(CLOSEABLE_HTTP_CLIENT, rootTarget, TELEMETRY_RUNTIME_CONSUMER); + EventsSender fetcher = EventsSender.create(SPLIT_HTTP_CLIENT, rootTarget, TELEMETRY_RUNTIME_CONSUMER); Assert.assertEquals("https://kubernetesturl.com/api/events/bulk", fetcher.getBulkEndpoint().toString()); } @Test public void testCustomURLAppendingPath() throws URISyntaxException { + SplitHttpClient SPLIT_HTTP_CLIENT = SplitHttpClientImpl.create(CLOSEABLE_HTTP_CLIENT, + new RequestDecorator(null), "qwerty", metadata()); URI rootTarget = URI.create("https://kubernetesturl.com/split/"); - EventsSender fetcher = EventsSender.create(CLOSEABLE_HTTP_CLIENT, rootTarget, TELEMETRY_RUNTIME_CONSUMER); + EventsSender fetcher = EventsSender.create(SPLIT_HTTP_CLIENT, rootTarget, TELEMETRY_RUNTIME_CONSUMER); Assert.assertEquals("https://kubernetesturl.com/split/api/events/bulk", fetcher.getBulkEndpoint().toString()); } @Test public void testCustomURLAppendingPathNoBackslash() throws URISyntaxException { + SplitHttpClient SPLIT_HTTP_CLIENT = SplitHttpClientImpl.create(CLOSEABLE_HTTP_CLIENT, + new RequestDecorator(null), "qwerty", metadata()); URI rootTarget = URI.create("https://kubernetesturl.com/split"); - EventsSender fetcher = EventsSender.create(CLOSEABLE_HTTP_CLIENT, rootTarget, TELEMETRY_RUNTIME_CONSUMER); + EventsSender fetcher = EventsSender.create(SPLIT_HTTP_CLIENT, rootTarget, TELEMETRY_RUNTIME_CONSUMER); Assert.assertEquals("https://kubernetesturl.com/split/api/events/bulk", fetcher.getBulkEndpoint().toString()); } -} \ No newline at end of file + + @Test + public void testHttpError() throws URISyntaxException, IOException, InvocationTargetException, + IllegalAccessException, NoSuchMethodException { + URI rootTarget = URI.create("https://kubernetesturl.com/split"); + CloseableHttpClient httpClient = TestHelper.mockHttpClient("", HttpStatus.SC_BAD_REQUEST); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + EventsSender sender = EventsSender.create(splitHtpClient, rootTarget, TELEMETRY_RUNTIME_CONSUMER); + // should not raise exception + sender.sendEvents(new ArrayList<>()); + } + + private SDKMetadata metadata() { + return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); + } + +} diff --git a/client/src/test/java/io/split/client/impressions/HttpImpressionsSenderTest.java b/client/src/test/java/io/split/client/impressions/HttpImpressionsSenderTest.java index ee586ffe4..18a4141cb 100644 --- a/client/src/test/java/io/split/client/impressions/HttpImpressionsSenderTest.java +++ b/client/src/test/java/io/split/client/impressions/HttpImpressionsSenderTest.java @@ -3,9 +3,13 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import io.split.TestHelper; +import io.split.client.RequestDecorator; import io.split.client.dtos.ImpressionCount; import io.split.client.dtos.KeyImpression; import io.split.client.dtos.TestImpressions; +import io.split.client.utils.SDKMetadata; +import io.split.service.SplitHttpClient; +import io.split.service.SplitHttpClientImpl; import io.split.telemetry.storage.InMemoryTelemetryStorage; import io.split.telemetry.storage.TelemetryStorage; import org.apache.hc.client5.http.classic.methods.HttpPost; @@ -24,6 +28,7 @@ import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -42,43 +47,63 @@ public class HttpImpressionsSenderTest { public void testDefaultURL() throws URISyntaxException { URI rootTarget = URI.create("https://api.split.io"); CloseableHttpClient httpClient = HttpClients.custom().build(); - HttpImpressionsSender fetcher = HttpImpressionsSender.create(httpClient, rootTarget, ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); - Assert.assertThat(fetcher.getTarget().toString(), Matchers.is(Matchers.equalTo("https://api.split.io/api/testImpressions/bulk"))); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + HttpImpressionsSender fetcher = HttpImpressionsSender.create(splitHtpClient, rootTarget, + ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); + Assert.assertThat(fetcher.getTarget().toString(), + Matchers.is(Matchers.equalTo("https://api.split.io/api/testImpressions/bulk"))); } @Test public void testCustomURLNoPathNoBackslash() throws URISyntaxException { URI rootTarget = URI.create("https://kubernetesturl.com"); CloseableHttpClient httpClient = HttpClients.custom().build(); - HttpImpressionsSender fetcher = HttpImpressionsSender.create(httpClient, rootTarget, ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); - Assert.assertThat(fetcher.getTarget().toString(), Matchers.is(Matchers.equalTo("https://kubernetesturl.com/api/testImpressions/bulk"))); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + HttpImpressionsSender fetcher = HttpImpressionsSender.create(splitHtpClient, rootTarget, + ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); + Assert.assertThat(fetcher.getTarget().toString(), + Matchers.is(Matchers.equalTo("https://kubernetesturl.com/api/testImpressions/bulk"))); } @Test public void testCustomURLAppendingPath() throws URISyntaxException { URI rootTarget = URI.create("https://kubernetesturl.com/split/"); CloseableHttpClient httpClient = HttpClients.custom().build(); - HttpImpressionsSender fetcher = HttpImpressionsSender.create(httpClient, rootTarget, ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); - Assert.assertThat(fetcher.getTarget().toString(), Matchers.is(Matchers.equalTo("https://kubernetesturl.com/split/api/testImpressions/bulk"))); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + HttpImpressionsSender fetcher = HttpImpressionsSender.create(splitHtpClient, rootTarget, + ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); + Assert.assertThat(fetcher.getTarget().toString(), + Matchers.is(Matchers.equalTo("https://kubernetesturl.com/split/api/testImpressions/bulk"))); } @Test public void testCustomURLAppendingPathNoBackslash() throws URISyntaxException { URI rootTarget = URI.create("https://kubernetesturl.com/split"); CloseableHttpClient httpClient = HttpClients.custom().build(); - HttpImpressionsSender fetcher = HttpImpressionsSender.create(httpClient, rootTarget, ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); - Assert.assertThat(fetcher.getTarget().toString(), Matchers.is(Matchers.equalTo("https://kubernetesturl.com/split/api/testImpressions/bulk"))); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + HttpImpressionsSender fetcher = HttpImpressionsSender.create(splitHtpClient, rootTarget, + ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); + Assert.assertThat(fetcher.getTarget().toString(), + Matchers.is(Matchers.equalTo("https://kubernetesturl.com/split/api/testImpressions/bulk"))); } @Test - public void testImpressionCountsEndpointOptimized() throws URISyntaxException, IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + public void testImpressionCountsEndpointOptimized() throws URISyntaxException, IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException { URI rootTarget = URI.create("https://kubernetesturl.com/split"); // Setup response mock CloseableHttpClient httpClient = TestHelper.mockHttpClient("", HttpStatus.SC_OK); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); // Send counters - HttpImpressionsSender sender = HttpImpressionsSender.create(httpClient, rootTarget, ImpressionsManager.Mode.OPTIMIZED, TELEMETRY_STORAGE); + HttpImpressionsSender sender = HttpImpressionsSender.create(splitHtpClient, rootTarget, + ImpressionsManager.Mode.OPTIMIZED, TELEMETRY_STORAGE); HashMap toSend = new HashMap<>(); toSend.put(new ImpressionCounter.Key("test1", 0), 4); toSend.put(new ImpressionCounter.Key("test2", 0), 5); @@ -88,8 +113,8 @@ public void testImpressionCountsEndpointOptimized() throws URISyntaxException, I ArgumentCaptor captor = ArgumentCaptor.forClass(HttpUriRequest.class); verify(httpClient).execute(captor.capture()); HttpUriRequest request = captor.getValue(); - assertThat(request.getUri(), is(equalTo(URI.create("https://kubernetesturl.com/split/api/testImpressions/count")))); - assertThat(request.getHeaders().length, is(0)); + assertThat(request.getUri(), + is(equalTo(URI.create("https://kubernetesturl.com/split/api/testImpressions/count")))); assertThat(request, instanceOf(HttpPost.class)); HttpPost asPostRequest = (HttpPost) request; InputStreamReader reader = new InputStreamReader(asPostRequest.getEntity().getContent()); @@ -101,14 +126,18 @@ public void testImpressionCountsEndpointOptimized() throws URISyntaxException, I } @Test - public void testImpressionCountsEndpointDebug() throws URISyntaxException, IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + public void testImpressionCountsEndpointDebug() throws URISyntaxException, IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException { URI rootTarget = URI.create("https://kubernetesturl.com/split"); // Setup response mock CloseableHttpClient httpClient = TestHelper.mockHttpClient("", HttpStatus.SC_OK); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); // Send counters - HttpImpressionsSender sender = HttpImpressionsSender.create(httpClient, rootTarget, ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); + HttpImpressionsSender sender = HttpImpressionsSender.create(splitHtpClient, rootTarget, + ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); HashMap toSend = new HashMap<>(); toSend.put(new ImpressionCounter.Key("test1", 0), 4); toSend.put(new ImpressionCounter.Key("test2", 0), 5); @@ -119,49 +148,74 @@ public void testImpressionCountsEndpointDebug() throws URISyntaxException, IOExc } @Test - public void testImpressionBulksEndpoint() throws URISyntaxException, IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + public void testImpressionBulksEndpoint() throws URISyntaxException, IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException { URI rootTarget = URI.create("https://kubernetesturl.com/split"); // Setup response mock CloseableHttpClient httpClient = TestHelper.mockHttpClient("", HttpStatus.SC_OK); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); - HttpImpressionsSender sender = HttpImpressionsSender.create(httpClient, rootTarget, ImpressionsManager.Mode.OPTIMIZED, TELEMETRY_STORAGE); + HttpImpressionsSender sender = HttpImpressionsSender.create(splitHtpClient, rootTarget, + ImpressionsManager.Mode.OPTIMIZED, TELEMETRY_STORAGE); // 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)) - ))); + 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))))); sender.postImpressionsBulk(toSend); // Capture outgoing request and validate it ArgumentCaptor captor = ArgumentCaptor.forClass(HttpUriRequest.class); verify(httpClient).execute(captor.capture()); HttpUriRequest request = captor.getValue(); - assertThat(request.getUri(), is(equalTo(URI.create("https://kubernetesturl.com/split/api/testImpressions/bulk")))); - assertThat(request.getHeaders().length, is(1)); + assertThat(request.getUri(), + is(equalTo(URI.create("https://kubernetesturl.com/split/api/testImpressions/bulk")))); assertThat(request.getFirstHeader("SplitSDKImpressionsMode").getValue(), is(equalTo("OPTIMIZED"))); assertThat(request, instanceOf(HttpPost.class)); HttpPost asPostRequest = (HttpPost) request; InputStreamReader reader = new InputStreamReader(asPostRequest.getEntity().getContent()); Gson gson = new Gson(); - List payload = gson.fromJson(reader, new TypeToken>() { }.getType()); + List payload = gson.fromJson(reader, new TypeToken>() { + }.getType()); assertThat(payload.size(), is(equalTo(2))); // Do the same flow for imrpessionsMode = debug CloseableHttpClient httpClientDebugMode = TestHelper.mockHttpClient("", HttpStatus.SC_OK); + SplitHttpClient splitHtpClient2 = SplitHttpClientImpl.create(httpClientDebugMode, new RequestDecorator(null), + "qwerty", metadata()); - sender = HttpImpressionsSender.create(httpClientDebugMode, rootTarget, ImpressionsManager.Mode.DEBUG, TELEMETRY_STORAGE); + sender = HttpImpressionsSender.create(splitHtpClient2, rootTarget, ImpressionsManager.Mode.DEBUG, + TELEMETRY_STORAGE); sender.postImpressionsBulk(toSend); captor = ArgumentCaptor.forClass(HttpUriRequest.class); verify(httpClientDebugMode).execute(captor.capture()); request = captor.getValue(); - assertThat(request.getHeaders().length, is(1)); assertThat(request.getFirstHeader("SplitSDKImpressionsMode").getValue(), is(equalTo("DEBUG"))); } + + @Test + public void testHttpError() throws URISyntaxException, IOException, IllegalAccessException, NoSuchMethodException, + InvocationTargetException { + URI rootTarget = URI.create("https://kubernetesturl.com/split"); + CloseableHttpClient httpClient = TestHelper.mockHttpClient("", HttpStatus.SC_BAD_REQUEST); + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + HttpImpressionsSender sender = HttpImpressionsSender.create(splitHtpClient, rootTarget, + ImpressionsManager.Mode.OPTIMIZED, TELEMETRY_STORAGE); + // Should not raise exception + sender.postImpressionsBulk(new ArrayList<>()); + sender.postCounters(new HashMap<>()); + } + + private SDKMetadata metadata() { + return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); + } + } diff --git a/client/src/test/java/io/split/client/utils/CustomDispatcher.java b/client/src/test/java/io/split/client/utils/CustomDispatcher.java index 62316b7a0..0b680156b 100644 --- a/client/src/test/java/io/split/client/utils/CustomDispatcher.java +++ b/client/src/test/java/io/split/client/utils/CustomDispatcher.java @@ -9,16 +9,16 @@ import java.util.*; public class CustomDispatcher extends Dispatcher { - public static final String INITIAL_SPLIT_CHANGES = "/api/splitChanges?since=-1"; - public static final String INITIAL_FLAGS_BY_SETS = "/api/splitChanges?since=-1&sets=set1%2Cset2"; - public static final String SINCE_1602796638344 = "/api/splitChanges?since=1602796638344&sets=set1%2Cset2"; - public static final String AUTH_ENABLED = "/api/auth/enabled"; - public static final String AUTH_DISABLED = "/api/auth/disabled"; - public static final String SINCE_1585948850109 = "/api/splitChanges?since=1585948850109"; - public static final String SINCE_1585948850109_FLAG_SET = "/api/splitChanges?since=-1&sets=set_1%2Cset_2"; - public static final String SINCE_1585948850110 = "/api/splitChanges?since=1585948850110"; - public static final String SINCE_1585948850111 = "/api/splitChanges?since=1585948850111"; - public static final String SINCE_1585948850112 = "/api/splitChanges?since=1585948850112"; + public static final String INITIAL_SPLIT_CHANGES = "/api/splitChanges?s=1.1&since=-1"; + public static final String INITIAL_FLAGS_BY_SETS = "/api/splitChanges?s=1.1&since=-1&sets=set1%2Cset2"; + public static final String SINCE_1602796638344 = "/api/splitChanges?s=1.1&since=1602796638344&sets=set1%2Cset2"; + public static final String AUTH_ENABLED = "/api/auth/enabled?s=1.1"; + public static final String AUTH_DISABLED = "/api/auth/disabled?s=1.1"; + public static final String SINCE_1585948850109 = "/api/splitChanges?s=1.1&since=1585948850109"; + public static final String SINCE_1585948850109_FLAG_SET = "/api/splitChanges?s=1.1&since=-1&sets=set_1%2Cset_2"; + public static final String SINCE_1585948850110 = "/api/splitChanges?s=1.1&since=1585948850110"; + public static final String SINCE_1585948850111 = "/api/splitChanges?s=1.1&since=1585948850111"; + public static final String SINCE_1585948850112 = "/api/splitChanges?s=1.1&since=1585948850112"; public static final String SEGMENT_TEST_INITIAL = "/api/segmentChanges/segment-test?since=-1"; public static final String SEGMENT3_INITIAL = "/api/segmentChanges/segment3?since=-1"; public static final String SEGMENT3_SINCE_1585948850110 = "/api/segmentChanges/segment3?since=1585948850110"; diff --git a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java index 634e93c62..e9c0e63a5 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java @@ -1,11 +1,21 @@ package io.split.engine.experiments; import com.google.common.collect.Lists; +import io.split.client.dtos.Condition; +import io.split.client.dtos.DataType; +import io.split.client.dtos.Matcher; +import io.split.client.dtos.MatcherCombiner; +import io.split.client.dtos.MatcherType; +import io.split.client.dtos.Partition; +import io.split.client.dtos.SegmentChange; +import io.split.client.dtos.Split; +import io.split.client.dtos.SplitChange; +import io.split.client.dtos.Status; import io.split.storages.SegmentCache; import io.split.storages.memory.SegmentCacheInMemoryImpl; -import io.split.client.dtos.*; +import io.split.client.utils.Json; +import io.split.engine.evaluator.Labels; import io.split.engine.ConditionsTestUtil; -import io.split.engine.SDKReadinessGates; import io.split.engine.matchers.AttributeMatcher; import io.split.engine.matchers.BetweenMatcher; import io.split.engine.matchers.CombiningMatcher; @@ -22,12 +32,14 @@ import io.split.engine.matchers.strings.StartsWithAnyOfMatcher; import io.split.engine.segments.SegmentChangeFetcher; import io.split.grammar.Treatments; -import io.split.telemetry.storage.InMemoryTelemetryStorage; -import io.split.telemetry.storage.TelemetryStorage; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -36,9 +48,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; /** * Tests for ExperimentParser @@ -50,11 +60,9 @@ public class SplitParserTest { public static final String EMPLOYEES = "employees"; public static final String SALES_PEOPLE = "salespeople"; public static final int CONDITIONS_UPPER_LIMIT = 50; - private static final TelemetryStorage TELEMETRY_STORAGE = Mockito.mock(InMemoryTelemetryStorage.class); @Test public void works() { - SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>(), 1L); segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>(), 1L); @@ -87,12 +95,11 @@ public void works() { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); + Assert.assertEquals(actual, expected); } @Test public void worksWithConfig() { - SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>(), 1L); segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>(), 1L); @@ -129,12 +136,12 @@ public void worksWithConfig() { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, configurations, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); - assertThat(actual.configurations().get("on"), is(equalTo(configurations.get("on")))); + Assert.assertEquals(actual, expected); + Assert.assertEquals(actual.configurations().get("on"), configurations.get("on")); } @Test - public void works_for_two_conditions() { + public void worksForTwoConditions() { SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>(), 1L); segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>(), 1L); @@ -168,12 +175,11 @@ public void works_for_two_conditions() { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfParsedConditions, "user", 1, 1, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); + Assert.assertEquals(actual, expected); } @Test - public void success_for_long_conditions() { - SDKReadinessGates gates = new SDKReadinessGates(); + public void successForLongConditions() { SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>(), 1L); segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>(), 1L); @@ -200,8 +206,7 @@ public void success_for_long_conditions() { @Test - public void works_with_attributes() { - SDKReadinessGates gates = new SDKReadinessGates(); + public void worksWithAttributes() { SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>(), 1L); segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>(), 1L); @@ -239,17 +244,11 @@ public void works_with_attributes() { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); + Assert.assertEquals(actual, expected); } @Test - public void less_than_or_equal_to() { - - -// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); -// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SDKReadinessGates gates = new SDKReadinessGates(); - SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + public void lessThanOrEqualTo() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); @@ -278,16 +277,11 @@ public void less_than_or_equal_to() { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); + Assert.assertEquals(actual, expected); } @Test - public void equal_to() { - -// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); -// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SDKReadinessGates gates = new SDKReadinessGates(); - SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + public void equalTo() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); @@ -315,16 +309,11 @@ public void equal_to() { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); + Assert.assertEquals(actual, expected); } @Test - public void equal_to_negative_number() { - -// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); -// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SDKReadinessGates gates = new SDKReadinessGates(); - SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + public void equalToNegativeNumber() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); @@ -351,16 +340,11 @@ public void equal_to_negative_number() { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); + Assert.assertEquals(actual, expected); } @Test public void between() { - -// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); -// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SDKReadinessGates gates = new SDKReadinessGates(); - SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); @@ -392,12 +376,11 @@ public void between() { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); + Assert.assertEquals(actual, expected); } @Test - public void contains_any_of_set() { - + public void containsAnyOfSet() { ArrayList set = Lists.newArrayList("sms", "voice"); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -411,12 +394,11 @@ public void contains_any_of_set() { ContainsAnyOfSetMatcher m = new ContainsAnyOfSetMatcher(set); - set_matcher_test(c, m); + setMatcherTest(c, m); } @Test - public void contains_all_of_set() { - + public void containsAllOfSet() { ArrayList set = Lists.newArrayList("sms", "voice"); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -430,12 +412,11 @@ public void contains_all_of_set() { ContainsAllOfSetMatcher m = new ContainsAllOfSetMatcher(set); - set_matcher_test(c, m); + setMatcherTest(c, m); } @Test - public void equal_to_set() { - + public void equalToSet() { ArrayList set = Lists.newArrayList("sms", "voice"); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -449,12 +430,11 @@ public void equal_to_set() { EqualToSetMatcher m = new EqualToSetMatcher(set); - set_matcher_test(c, m); + setMatcherTest(c, m); } @Test - public void is_part_of_set() { - + public void isPartOfSet() { ArrayList set = Lists.newArrayList("sms", "voice"); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -468,12 +448,11 @@ public void is_part_of_set() { PartOfSetMatcher m = new PartOfSetMatcher(set); - set_matcher_test(c, m); + setMatcherTest(c, m); } @Test - public void starts_with_string() { - + public void startsWithString() { ArrayList set = Lists.newArrayList("sms", "voice"); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -487,12 +466,11 @@ public void starts_with_string() { StartsWithAnyOfMatcher m = new StartsWithAnyOfMatcher(set); - set_matcher_test(c, m); + setMatcherTest(c, m); } @Test - public void ends_with_string() { - + public void endsWithString() { ArrayList set = Lists.newArrayList("sms", "voice"); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -506,13 +484,12 @@ public void ends_with_string() { EndsWithAnyOfMatcher m = new EndsWithAnyOfMatcher(set); - set_matcher_test(c, m); + setMatcherTest(c, m); } @Test - public void contains_string() { - + public void containsString() { ArrayList set = Lists.newArrayList("sms", "voice"); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -526,15 +503,144 @@ public void contains_string() { ContainsAnyOfMatcher m = new ContainsAnyOfMatcher(set); - set_matcher_test(c, m); + setMatcherTest(c, m); } - public void set_matcher_test(Condition c, io.split.engine.matchers.Matcher m) { + @Test + public void UnsupportedMatcher() { + SplitParser parser = new SplitParser(); + String splitWithUndefinedMatcher = "{\"since\":-1,\"till\": 1457726098069,\"splits\": [{ \"changeNumber\": 123, \"trafficTypeName\": \"user\", \"name\": \"some_name\"," + + "\"trafficAllocation\": 100, \"trafficAllocationSeed\": 123456, \"seed\": 321654, \"status\": \"ACTIVE\"," + + "\"killed\": false, \"defaultTreatment\": \"off\", \"algo\": 2,\"conditions\": [{ \"partitions\": [" + + "{\"treatment\": \"on\", \"size\": 50}, {\"treatment\": \"off\", \"size\": 50}], \"contitionType\": \"ROLLOUT\"," + + "\"label\": \"some_label\", \"matcherGroup\": { \"matchers\": [{ \"matcherType\": \"UNKNOWN\", \"negate\": false}]," + + "\"combiner\": \"AND\"}}], \"sets\": [\"set1\"]}]}"; + SplitChange change = Json.fromJson(splitWithUndefinedMatcher, SplitChange.class); + for (Split split : change.splits) { + // should not cause exception + ParsedSplit parsedSplit = parser.parse(split); + for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { + assertTrue(parsedCondition.label() == Labels.UNSUPPORTED_MATCHER); + for (AttributeMatcher matcher : parsedCondition.matcher().attributeMatchers()) { + // Check the matcher is ALL_KEYS + assertTrue(matcher.matcher().toString().equals(" in segment all")); + } + } + } + } + + @Test + public void EqualToSemverMatcher() throws IOException { + SplitParser parser = new SplitParser(); + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/semver/semver-splits.json")), StandardCharsets.UTF_8); + SplitChange change = Json.fromJson(splits, SplitChange.class); + for (Split split : change.splits) { + // should not cause exception + ParsedSplit parsedSplit = parser.parse(split); + if (split.name.equals("semver_equalto")) { + for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { + assertTrue(parsedCondition.label().equals("equal to semver")); + for (AttributeMatcher matcher : parsedCondition.matcher().attributeMatchers()) { + // Check the matcher is ALL_KEYS + assertTrue(matcher.matcher().toString().equals(" == semver 1\\.22\\.9")); + return; + } + } + } + } + assertTrue(false); + } + + @Test + public void GreaterThanOrEqualSemverMatcher() throws IOException { + SplitParser parser = new SplitParser(); + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/semver/semver-splits.json")), StandardCharsets.UTF_8); + SplitChange change = Json.fromJson(splits, SplitChange.class); + for (Split split : change.splits) { + // should not cause exception + ParsedSplit parsedSplit = parser.parse(split); + if (split.name.equals("semver_greater_or_equalto")) { + for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { + assertTrue(parsedCondition.label().equals("greater than or equal to semver")); + for (AttributeMatcher matcher : parsedCondition.matcher().attributeMatchers()) { + // Check the matcher is ALL_KEYS + assertTrue(matcher.matcher().toString().equals(" >= semver 1\\.22\\.9")); + return; + } + } + } + } + assertTrue(false); + } + + @Test + public void LessThanOrEqualSemverMatcher() throws IOException { + SplitParser parser = new SplitParser(); + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/semver/semver-splits.json")), StandardCharsets.UTF_8); + SplitChange change = Json.fromJson(splits, SplitChange.class); + for (Split split : change.splits) { + // should not cause exception + ParsedSplit parsedSplit = parser.parse(split); + if (split.name.equals("semver_less_or_equalto")) { + for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { + assertTrue(parsedCondition.label().equals("less than or equal to semver")); + for (AttributeMatcher matcher : parsedCondition.matcher().attributeMatchers()) { + // Check the matcher is ALL_KEYS + assertTrue(matcher.matcher().toString().equals(" <= semver 1\\.22\\.9")); + return; + } + } + } + } + assertTrue(false); + } + + @Test + public void BetweenSemverMatcher() throws IOException { + SplitParser parser = new SplitParser(); + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/semver/semver-splits.json")), StandardCharsets.UTF_8); + SplitChange change = Json.fromJson(splits, SplitChange.class); + for (Split split : change.splits) { + // should not cause exception + ParsedSplit parsedSplit = parser.parse(split); + if (split.name.equals("semver_between")) { + for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { + assertTrue(parsedCondition.label().equals("between semver")); + for (AttributeMatcher matcher : parsedCondition.matcher().attributeMatchers()) { + // Check the matcher is ALL_KEYS + assertTrue(matcher.matcher().toString().equals(" between semver 1\\.22\\.9 and 2\\.1\\.0")); + return; + } + } + } + } + assertTrue(false); + } + + @Test + public void InListSemverMatcher() throws IOException { + SplitParser parser = new SplitParser(); + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/semver/semver-splits.json")), StandardCharsets.UTF_8); + SplitChange change = Json.fromJson(splits, SplitChange.class); + for (Split split : change.splits) { + // should not cause exception + ParsedSplit parsedSplit = parser.parse(split); + if (split.name.equals("semver_inlist")) { + for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { + assertTrue(parsedCondition.label().equals("in list semver")); + for (AttributeMatcher matcher : parsedCondition.matcher().attributeMatchers()) { + // Check the matcher is ALL_KEYS + assertTrue(matcher.matcher().toString().startsWith(" in semver list")); + return; + } + } + } + } + assertTrue(false); + } + + public void setMatcherTest(Condition c, io.split.engine.matchers.Matcher m) { -// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); -// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SDKReadinessGates gates = new SDKReadinessGates(); - SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); @@ -560,7 +666,7 @@ public void set_matcher_test(Condition c, io.split.engine.matchers.Matcher m) { ParsedSplit expected = ParsedSplit.createParsedSplitForTests("splitName", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>()); - assertThat(actual, is(equalTo(expected))); + Assert.assertEquals(actual, expected); } private Split makeSplit(String name, int seed, List conditions, long changeNumber) { @@ -592,5 +698,4 @@ private SegmentChange getSegmentChange(long since, long till, String segmentName segmentChange.removed = new ArrayList<>(); return segmentChange; } - -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/matchers/AttributeMatcherTest.java b/client/src/test/java/io/split/engine/matchers/AttributeMatcherTest.java index 7c723adb6..9f535790d 100644 --- a/client/src/test/java/io/split/engine/matchers/AttributeMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/AttributeMatcherTest.java @@ -5,6 +5,7 @@ import com.google.common.collect.Maps; import io.split.client.dtos.DataType; import io.split.engine.matchers.strings.WhitelistMatcher; +import org.junit.Assert; import org.junit.Test; import java.util.Calendar; @@ -12,9 +13,6 @@ import java.util.Date; import java.util.Map; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Tests for AllKeysMatcher */ @@ -23,60 +21,62 @@ public class AttributeMatcherTest { @Test public void works() { AttributeMatcher matcher = new AttributeMatcher("creation_date", new GreaterThanOrEqualToMatcher(100L, DataType.NUMBER), false); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 99L), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 100L), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101.3), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", Calendar.getInstance()), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", new Date()), null), is(false)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 99L), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 100L), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101.3), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", Calendar.getInstance()), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", new Date()), null)); } @Test - public void works_negation() { + public void worksNegation() { AttributeMatcher matcher = new AttributeMatcher("creation_date", new GreaterThanOrEqualToMatcher(100L, DataType.NUMBER), true); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 99L), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 100L), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101.3), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", Calendar.getInstance()), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", new Date()), null), is(true)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 99L), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 99L), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 100L), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101.3), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", Calendar.getInstance()), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", new Date()), null)); } @Test - public void works_less_than_or_equal_to() { + public void worksLessThanOrEqualTo() { AttributeMatcher matcher = new AttributeMatcher("creation_date", new LessThanOrEqualToMatcher(100L, DataType.NUMBER), false); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 99L), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 100L), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101.3), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", Calendar.getInstance()), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", new Date()), null), is(false)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 99L), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 100L), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101.3), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 101.3), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", Calendar.getInstance()), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", new Date()), null)); } @Test - public void works_boolean() { + public void worksBoolean() { AttributeMatcher matcher = new AttributeMatcher("value", new BooleanMatcher(true), false); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", true), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", "true"), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", "True"), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", "TrUe"), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", "TRUE"), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", false), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", "false"), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", "False"), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", "FALSE"), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", "faLSE"), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", ""), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", 0), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("value", 1), null), is(false)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("value", true), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("value", "true"), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("value", "True"), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("value", "TrUe"), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("value", "TRUE"), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("value", false), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("value", "false"), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("value", "False"), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("value", "FALSE"), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("value", "faLSE"), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("value", ""), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("value", 0), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("value", 1), null)); } @Test - public void error_conditions() { + public void errorConditions() { AttributeMatcher matcher = new AttributeMatcher("creation_date", new GreaterThanOrEqualToMatcher(100L, DataType.NUMBER), false); - assertThat(matcher.match("ignore", null, null, null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("foo", 101), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", "101"), null), is(false)); + Assert.assertFalse(matcher.match("ignore", null, null, null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("foo", 101), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", "101"), null)); } @Test @@ -85,38 +85,38 @@ public void dates() { Calendar c = Calendar.getInstance(); c.add(Calendar.YEAR, -1); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", c.getTimeInMillis()), null), is(false)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", c.getTimeInMillis()), null)); c.add(Calendar.YEAR, 2); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", c.getTimeInMillis()), null), is(true)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", c.getTimeInMillis()), null)); } @Test public void between() { AttributeMatcher matcher = new AttributeMatcher("creation_date", new BetweenMatcher(10, 12, DataType.NUMBER), false); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 9), null), is(false)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 10), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 11), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 12), null), is(true)); - assertThat(matcher.match("ignore", null, ImmutableMap.of("creation_date", 13), null), is(false)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 9), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 10), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 11), null)); + Assert.assertTrue(matcher.match("ignore", null, ImmutableMap.of("creation_date", 12), null)); + Assert.assertFalse(matcher.match("ignore", null, ImmutableMap.of("creation_date", 13), null)); } @Test - public void when_no_attribute_we_use_the_key() { + public void whenNoAttributeWeUseTheKey() { AttributeMatcher matcher = new AttributeMatcher(null, new WhitelistMatcher(Lists.newArrayList("trial")), false); Map nullMap = Maps.newHashMap(); nullMap.put("planType", null); - assertThat(matcher.match("trial", null, ImmutableMap.of("planType", "trial"), null), is(true)); - assertThat(matcher.match("trial", null, ImmutableMap.of("planType", "Trial"), null), is(true)); - assertThat(matcher.match("trial", null, nullMap, null), is(true)); - assertThat(matcher.match("trial", null, ImmutableMap.of("planType", "premium"), null), is(true)); - assertThat(matcher.match("trial", null, ImmutableMap.of("planType", 10), null), is(true)); - assertThat(matcher.match("trial", null, Collections.emptyMap(), null), is(true)); - assertThat(matcher.match("trial", null, null, null), is(true)); - assertThat(matcher.match("premium", null, null, null), is(false)); + Assert.assertTrue(matcher.match("trial", null, ImmutableMap.of("planType", "trial"), null)); + Assert.assertTrue(matcher.match("trial", null, ImmutableMap.of("planType", "Trial"), null)); + Assert.assertTrue(matcher.match("trial", null, nullMap, null)); + Assert.assertTrue(matcher.match("trial", null, ImmutableMap.of("planType", "premium"), null)); + Assert.assertTrue(matcher.match("trial", null, ImmutableMap.of("planType", 10), null)); + Assert.assertTrue(matcher.match("trial", null, Collections.emptyMap(), null)); + Assert.assertTrue(matcher.match("trial", null, null, null)); + Assert.assertFalse(matcher.match("premium", null, null, null)); } -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/matchers/BetweenMatcherTest.java b/client/src/test/java/io/split/engine/matchers/BetweenMatcherTest.java index 410977699..22bc3b449 100644 --- a/client/src/test/java/io/split/engine/matchers/BetweenMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/BetweenMatcherTest.java @@ -1,11 +1,9 @@ package io.split.engine.matchers; import io.split.client.dtos.DataType; +import org.junit.Assert; import org.junit.Test; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Tests for BetweenMatcherTest */ @@ -18,18 +16,18 @@ public void works() { BetweenMatcher matcher = new BetweenMatcher(start, end, DataType.NUMBER); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); for (int i = start; i <= end; i++) { - assertThat(matcher.match(i, null, null, null), is(true)); + Assert.assertTrue(matcher.match(i, null, null, null)); } - assertThat(matcher.match(new Long(start - 1), null, null, null), is(false)); - assertThat(matcher.match(end + 1, null, null, null), is(false)); + Assert.assertFalse(matcher.match(new Long(start - 1), null, null, null)); + Assert.assertFalse(matcher.match(end + 1, null, null, null)); } @Test - public void works_dates() { + public void worksDates() { long april11_2016_23_59 = 1460419199000L; long april12_2016_midnight_19 = 1460420360000L; long april12_2016_midnight_20 = 1460420421903L; @@ -39,14 +37,10 @@ public void works_dates() { BetweenMatcher matcher = new BetweenMatcher(april12_2016_midnight_19, april12_2016_midnight_20_59, DataType.DATETIME); - assertThat(matcher.match(april11_2016_23_59, null, null, null), is(false)); - assertThat(matcher.match(april12_2016_midnight_19, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_midnight_20, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_midnight_20_59, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_1_20, null, null, null), is(false)); - - + Assert.assertFalse(matcher.match(april11_2016_23_59, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_19, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_20, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_20_59, null, null, null)); + Assert.assertFalse(matcher.match(april12_2016_1_20, null, null, null)); } - - -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/matchers/BetweenSemverMatcherTest.java b/client/src/test/java/io/split/engine/matchers/BetweenSemverMatcherTest.java new file mode 100644 index 000000000..41a4f76d4 --- /dev/null +++ b/client/src/test/java/io/split/engine/matchers/BetweenSemverMatcherTest.java @@ -0,0 +1,36 @@ +package io.split.engine.matchers; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +/** + * Tests for EqualToSemverMatcher + */ + +public class BetweenSemverMatcherTest { + + @Test + public void works() { + BetweenSemverMatcher betweenSemverMatcher = new BetweenSemverMatcher("2.1.8", "3.0.0"); + + assertTrue( betweenSemverMatcher.match("2.1.8", null, null, null)); + assertTrue( betweenSemverMatcher.match("2.1.9", null, null, null)); + assertFalse( betweenSemverMatcher.match("2.1.8-rc", null, null, null)); + assertTrue( betweenSemverMatcher.match("3.0.0+build", null, null, null)); + assertFalse( betweenSemverMatcher.match("4.5.8", null, null, null)); + assertFalse( betweenSemverMatcher.match("1.0.4", null, null, null)); + assertTrue(betweenSemverMatcher.equals(betweenSemverMatcher)); + assertTrue(betweenSemverMatcher.hashCode() != 0); + } + + @Test + public void testNull() { + BetweenSemverMatcher betweenSemverMatcher = new BetweenSemverMatcher("2.1.8", "3.0.0"); + assertFalse( betweenSemverMatcher.match(null, null, null, null)); + + betweenSemverMatcher = new BetweenSemverMatcher("2.www.8", "3.xx.0"); + assertFalse(betweenSemverMatcher.match("2.www.8", null, null, null)); + } +} diff --git a/client/src/test/java/io/split/engine/matchers/BooleanMatcherTest.java b/client/src/test/java/io/split/engine/matchers/BooleanMatcherTest.java index cb466bc0b..14889af68 100644 --- a/client/src/test/java/io/split/engine/matchers/BooleanMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/BooleanMatcherTest.java @@ -8,7 +8,7 @@ public class BooleanMatcherTest { @Test - public void works_true() { + public void worksTrue() { BooleanMatcher matcher = new BooleanMatcher(true); assertThat(matcher.match(null, null, null, null), is(false)); assertThat(matcher.match(true, null, null, null), is(true)); @@ -23,7 +23,7 @@ public void works_true() { } @Test - public void works_false() { + public void worksFalse() { BooleanMatcher matcher = new BooleanMatcher(false); assertThat(matcher.match(null, null, null, null), is(false)); assertThat(matcher.match(true, null, null, null), is(false)); diff --git a/client/src/test/java/io/split/engine/matchers/CombiningMatcherTest.java b/client/src/test/java/io/split/engine/matchers/CombiningMatcherTest.java index ee58a18a2..3946aed50 100644 --- a/client/src/test/java/io/split/engine/matchers/CombiningMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/CombiningMatcherTest.java @@ -3,13 +3,11 @@ import com.google.common.collect.Lists; import io.split.client.dtos.MatcherCombiner; import io.split.engine.matchers.strings.WhitelistMatcher; +import org.junit.Assert; import org.junit.Test; import java.util.Collections; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Tests CombiningMatcher * @@ -18,15 +16,14 @@ public class CombiningMatcherTest { @Test - public void works_and() { + public void worksAnd() { AttributeMatcher matcher1 = AttributeMatcher.vanilla(new AllKeysMatcher()); AttributeMatcher matcher2 = AttributeMatcher.vanilla(new WhitelistMatcher(Lists.newArrayList("a", "b"))); CombiningMatcher combiner = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(matcher1, matcher2)); - assertThat(combiner.match("a", null, null, null), is(true)); - assertThat(combiner.match("b", null, Collections.emptyMap(), null), is(true)); - assertThat(combiner.match("c", null, null, null), is(false)); + Assert.assertTrue(combiner.match("a", null, null, null)); + Assert.assertTrue(combiner.match("b", null, Collections.emptyMap(), null)); + Assert.assertFalse(combiner.match("c", null, null, null)); } - -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/matchers/EqualToMatcherTest.java b/client/src/test/java/io/split/engine/matchers/EqualToMatcherTest.java index aeb887f9c..f8320e511 100644 --- a/client/src/test/java/io/split/engine/matchers/EqualToMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/EqualToMatcherTest.java @@ -1,11 +1,9 @@ package io.split.engine.matchers; import io.split.client.dtos.DataType; +import org.junit.Assert; import org.junit.Test; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Tests for AllKeysMatcher */ @@ -14,30 +12,30 @@ public class EqualToMatcherTest { @Test public void works() { EqualToMatcher matcher = new EqualToMatcher(10, DataType.NUMBER); - assertThat(matcher.match(null, null, null, null), is(false)); - assertThat(matcher.match(1, null, null, null), is(false)); - assertThat(matcher.match(new Long(-1), null, null, null), is(false)); - assertThat(matcher.match(9, null, null, null), is(false)); - assertThat(matcher.match(new Long(10), null, null, null), is(true)); - assertThat(matcher.match(11, null, null, null), is(false)); - assertThat(matcher.match(100, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); + Assert.assertFalse(matcher.match(1, null, null, null)); + Assert.assertFalse(matcher.match(new Long(-1), null, null, null)); + Assert.assertFalse(matcher.match(9, null, null, null)); + Assert.assertTrue(matcher.match(new Long(10), null, null, null)); + Assert.assertFalse(matcher.match(11, null, null, null)); + Assert.assertFalse(matcher.match(100, null, null, null)); } @Test - public void works_negative() { + public void worksNegative() { EqualToMatcher matcher = new EqualToMatcher(-10, DataType.NUMBER); - assertThat(matcher.match(null, null, null, null), is(false)); - assertThat(matcher.match(1, null, null, null), is(false)); - assertThat(matcher.match(new Long(-1), null, null, null), is(false)); - assertThat(matcher.match(9, null, null, null), is(false)); - assertThat(matcher.match(new Long(10), null, null, null), is(false)); - assertThat(matcher.match(11, null, null, null), is(false)); - assertThat(matcher.match(-10, null, null, null), is(true)); - assertThat(matcher.match(-11, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); + Assert.assertFalse(matcher.match(1, null, null, null)); + Assert.assertFalse(matcher.match(new Long(-1), null, null, null)); + Assert.assertFalse(matcher.match(9, null, null, null)); + Assert.assertFalse(matcher.match(new Long(10), null, null, null)); + Assert.assertFalse(matcher.match(11, null, null, null)); + Assert.assertTrue(matcher.match(-10, null, null, null)); + Assert.assertFalse(matcher.match(-11, null, null, null)); } @Test - public void works_dates() { + public void worksDates() { long april11_2016_23_59_59 = 1460419199000L; long april12_2016_midnight_19 = 1460420360000L; long april12_2016_midnight_20 = 1460420421903L; @@ -45,12 +43,10 @@ public void works_dates() { long april13_2016_00_00_00 = 1460505600000L; EqualToMatcher matcher = new EqualToMatcher(april12_2016_midnight_20, DataType.DATETIME); - assertThat(matcher.match(april11_2016_23_59_59, null, null, null), is(false)); - assertThat(matcher.match(april12_2016_midnight_19, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_midnight_20, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_1_20, null, null, null), is(true)); - assertThat(matcher.match(april13_2016_00_00_00, null, null, null), is(false)); - + Assert.assertFalse(matcher.match(april11_2016_23_59_59, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_19, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_20, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_1_20, null, null, null)); + Assert.assertFalse(matcher.match(april13_2016_00_00_00, null, null, null)); } - -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/matchers/EqualToSemverMatcherTest.java b/client/src/test/java/io/split/engine/matchers/EqualToSemverMatcherTest.java new file mode 100644 index 000000000..a5a41e2bb --- /dev/null +++ b/client/src/test/java/io/split/engine/matchers/EqualToSemverMatcherTest.java @@ -0,0 +1,34 @@ +package io.split.engine.matchers; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +/** + * Tests for EqualToSemverMatcher + */ + +public class EqualToSemverMatcherTest { + + @Test + public void works() { + EqualToSemverMatcher equalToSemverMatcher = new EqualToSemverMatcher("2.1.8"); + + assertTrue( equalToSemverMatcher.match("2.1.8", null, null, null)); + assertFalse(equalToSemverMatcher.match("2.1.9", null, null, null)); + assertFalse(equalToSemverMatcher.match("2.1.8-rc", null, null, null)); + assertFalse( equalToSemverMatcher.match("2.1.8+build", null, null, null)); + assertTrue(equalToSemverMatcher.equals(equalToSemverMatcher)); + assertTrue(equalToSemverMatcher.hashCode() != 0); + } + + @Test + public void testNull() { + EqualToSemverMatcher equalToSemverMatcher = new EqualToSemverMatcher("2.1.8"); + assertFalse( equalToSemverMatcher.match(null, null, null, null)); + + equalToSemverMatcher = new EqualToSemverMatcher("2.ee.8"); + assertFalse(equalToSemverMatcher.match("2.ee.8", null, null, null)); + } +} diff --git a/client/src/test/java/io/split/engine/matchers/GreaterThanOrEqualToMatcherTest.java b/client/src/test/java/io/split/engine/matchers/GreaterThanOrEqualToMatcherTest.java index 566f53623..bbe37fdd7 100644 --- a/client/src/test/java/io/split/engine/matchers/GreaterThanOrEqualToMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/GreaterThanOrEqualToMatcherTest.java @@ -1,11 +1,9 @@ package io.split.engine.matchers; import io.split.client.dtos.DataType; +import org.junit.Assert; import org.junit.Test; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Tests for AllKeysMatcher */ @@ -14,31 +12,31 @@ public class GreaterThanOrEqualToMatcherTest { @Test public void works() { GreaterThanOrEqualToMatcher matcher = new GreaterThanOrEqualToMatcher(10, DataType.NUMBER); - assertThat(matcher.match(null, null, null, null), is(false)); - assertThat(matcher.match(1, null, null, null), is(false)); - assertThat(matcher.match(new Long(-1), null, null, null), is(false)); - assertThat(matcher.match(9, null, null, null), is(false)); - assertThat(matcher.match(new Long(10), null, null, null), is(true)); - assertThat(matcher.match(11, null, null, null), is(true)); - assertThat(matcher.match(100, null, null, null), is(true)); + Assert.assertFalse(matcher.match(null, null, null, null)); + Assert.assertFalse(matcher.match(1, null, null, null)); + Assert.assertFalse(matcher.match(new Long(-1), null, null, null)); + Assert.assertFalse(matcher.match(9, null, null, null)); + Assert.assertTrue(matcher.match(new Long(10), null, null, null)); + Assert.assertTrue(matcher.match(11, null, null, null)); + Assert.assertTrue(matcher.match(100, null, null, null)); } @Test - public void works_negative() { + public void worksNegative() { GreaterThanOrEqualToMatcher matcher = new GreaterThanOrEqualToMatcher(-10, DataType.NUMBER); - assertThat(matcher.match(null, null, null, null), is(false)); - assertThat(matcher.match(1, null, null, null), is(true)); - assertThat(matcher.match(new Long(-1), null, null, null), is(true)); - assertThat(matcher.match(9, null, null, null), is(true)); - assertThat(matcher.match(new Long(10), null, null, null), is(true)); - assertThat(matcher.match(11, null, null, null), is(true)); - assertThat(matcher.match(100, null, null, null), is(true)); - assertThat(matcher.match(-10, null, null, null), is(true)); - assertThat(matcher.match(-11, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); + Assert.assertTrue(matcher.match(1, null, null, null)); + Assert.assertTrue(matcher.match(new Long(-1), null, null, null)); + Assert.assertTrue(matcher.match(9, null, null, null)); + Assert.assertTrue(matcher.match(new Long(10), null, null, null)); + Assert.assertTrue(matcher.match(11, null, null, null)); + Assert.assertTrue(matcher.match(100, null, null, null)); + Assert.assertTrue(matcher.match(-10, null, null, null)); + Assert.assertFalse(matcher.match(-11, null, null, null)); } @Test - public void works_dates() { + public void worksDates() { long april12_2016_midnight_19 = 1460420360000L; long april12_2016_midnight_20 = 1460420421903L; long april12_2016_midnight_20_59 = 1460420459000L; @@ -46,11 +44,11 @@ public void works_dates() { long april12_2016_18_20 = 1460485239000L; GreaterThanOrEqualToMatcher matcher = new GreaterThanOrEqualToMatcher(april12_2016_midnight_20, DataType.DATETIME); - assertThat(matcher.match(april12_2016_midnight_19, null, null, null), is(false)); - assertThat(matcher.match(april12_2016_midnight_20_59, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_midnight_20, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_1_20, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_18_20, null, null, null), is(true)); + Assert.assertFalse(matcher.match(april12_2016_midnight_19, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_20_59, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_20_59, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_20, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_1_20, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_18_20, null, null, null)); } - -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/matchers/GreaterThanOrEqualToSemverMatcherTest.java b/client/src/test/java/io/split/engine/matchers/GreaterThanOrEqualToSemverMatcherTest.java new file mode 100644 index 000000000..753034c70 --- /dev/null +++ b/client/src/test/java/io/split/engine/matchers/GreaterThanOrEqualToSemverMatcherTest.java @@ -0,0 +1,34 @@ +package io.split.engine.matchers; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +/** + * Tests for EqualToSemverMatcher + */ + +public class GreaterThanOrEqualToSemverMatcherTest { + + @Test + public void works() { + GreaterThanOrEqualToSemverMatcher greaterThanOrEqualToSemverMatcher = new GreaterThanOrEqualToSemverMatcher("2.1.8"); + assertTrue( greaterThanOrEqualToSemverMatcher.match("2.1.8", null, null, null)); + assertTrue( greaterThanOrEqualToSemverMatcher.match("2.1.9", null, null, null)); + assertFalse( greaterThanOrEqualToSemverMatcher.match("2.1.8-rc", null, null, null)); + assertFalse( greaterThanOrEqualToSemverMatcher.match("2.0.10", null, null, null)); + assertTrue( greaterThanOrEqualToSemverMatcher.match("2.1.8+build", null, null, null)); + assertTrue(greaterThanOrEqualToSemverMatcher.equals(greaterThanOrEqualToSemverMatcher)); + assertTrue(greaterThanOrEqualToSemverMatcher.hashCode() != 0); + } + + @Test + public void testNull() { + GreaterThanOrEqualToSemverMatcher greaterThanOrEqualToSemverMatcher = new GreaterThanOrEqualToSemverMatcher("2.1.8"); + assertFalse( greaterThanOrEqualToSemverMatcher.match(null, null, null, null)); + + greaterThanOrEqualToSemverMatcher = new GreaterThanOrEqualToSemverMatcher("2.ee.8"); + assertFalse(greaterThanOrEqualToSemverMatcher.match("2.ee.8", null, null, null)); + } +} diff --git a/client/src/test/java/io/split/engine/matchers/InListSemverMatcherTest.java b/client/src/test/java/io/split/engine/matchers/InListSemverMatcherTest.java new file mode 100644 index 000000000..c01371251 --- /dev/null +++ b/client/src/test/java/io/split/engine/matchers/InListSemverMatcherTest.java @@ -0,0 +1,41 @@ +package io.split.engine.matchers; + +import com.google.common.collect.Lists; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for EqualToSemverMatcher + */ + +public class InListSemverMatcherTest { + + @Test + public void works() { + List whitelist = Lists.newArrayList("2.1.8", "3.4.0"); + InListSemverMatcher inListSemverMatcher = new InListSemverMatcher(whitelist); + + assertTrue( inListSemverMatcher.match("2.1.8", null, null, null) == true); + assertTrue( inListSemverMatcher.match("2.1.9", null, null, null) == false); + assertTrue( inListSemverMatcher.match("2.1.8-rc", null, null, null) == false); + assertTrue( inListSemverMatcher.match("3.4.0", null, null, null) == true); + assertTrue( inListSemverMatcher.match("3.4.0+build", null, null, null) == false); + assertTrue(inListSemverMatcher.equals(inListSemverMatcher)); + assertTrue(inListSemverMatcher.hashCode() != 0); + } + + @Test + public void testNull() { + List whitelist = Lists.newArrayList("2.1.8", "3.4.0"); + InListSemverMatcher inListSemverMatcher = new InListSemverMatcher(whitelist); + assertFalse( inListSemverMatcher.match(null, null, null, null)); + + whitelist = Lists.newArrayList("2.1.eee", "3.xxx.0"); + inListSemverMatcher = new InListSemverMatcher(whitelist); + assertFalse(inListSemverMatcher.match("2.1.eee", null, null, null)); + } +} diff --git a/client/src/test/java/io/split/engine/matchers/LessThanOrEqualToMatcherTest.java b/client/src/test/java/io/split/engine/matchers/LessThanOrEqualToMatcherTest.java index 1663a6085..47853ed4c 100644 --- a/client/src/test/java/io/split/engine/matchers/LessThanOrEqualToMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/LessThanOrEqualToMatcherTest.java @@ -1,11 +1,9 @@ package io.split.engine.matchers; import io.split.client.dtos.DataType; +import org.junit.Assert; import org.junit.Test; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Tests for AllKeysMatcher */ @@ -14,31 +12,31 @@ public class LessThanOrEqualToMatcherTest { @Test public void works() { LessThanOrEqualToMatcher matcher = new LessThanOrEqualToMatcher(10, DataType.NUMBER); - assertThat(matcher.match(null, null, null, null), is(false)); - assertThat(matcher.match(1, null, null, null), is(true)); - assertThat(matcher.match(new Long(-1), null, null, null), is(true)); - assertThat(matcher.match(9, null, null, null), is(true)); - assertThat(matcher.match(new Long(10), null, null, null), is(true)); - assertThat(matcher.match(11, null, null, null), is(false)); - assertThat(matcher.match(100, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); + Assert.assertTrue(matcher.match(1, null, null, null)); + Assert.assertTrue(matcher.match(new Long(-1), null, null, null)); + Assert.assertTrue(matcher.match(9, null, null, null)); + Assert.assertTrue(matcher.match(new Long(10), null, null, null)); + Assert.assertFalse(matcher.match(11, null, null, null)); + Assert.assertFalse(matcher.match(100, null, null, null)); } @Test - public void works_negative() { + public void worksNegative() { LessThanOrEqualToMatcher matcher = new LessThanOrEqualToMatcher(-10, DataType.NUMBER); - assertThat(matcher.match(null, null, null, null), is(false)); - assertThat(matcher.match(1, null, null, null), is(false)); - assertThat(matcher.match(new Long(-1), null, null, null), is(false)); - assertThat(matcher.match(9, null, null, null), is(false)); - assertThat(matcher.match(new Long(10), null, null, null), is(false)); - assertThat(matcher.match(11, null, null, null), is(false)); - assertThat(matcher.match(-9, null, null, null), is(false)); - assertThat(matcher.match(-10, null, null, null), is(true)); - assertThat(matcher.match(-11, null, null, null), is(true)); + Assert.assertFalse(matcher.match(null, null, null, null)); + Assert.assertFalse(matcher.match(1, null, null, null)); + Assert.assertFalse(matcher.match(new Long(-1), null, null, null)); + Assert.assertFalse(matcher.match(9, null, null, null)); + Assert.assertFalse(matcher.match(new Long(10), null, null, null)); + Assert.assertFalse(matcher.match(11, null, null, null)); + Assert.assertFalse(matcher.match(-9, null, null, null)); + Assert.assertTrue(matcher.match(-10, null, null, null)); + Assert.assertTrue(matcher.match(-11, null, null, null)); } @Test - public void works_dates() { + public void worksDates() { long april11_2016_23_59 = 1460419199000L; long april12_2016_midnight_19 = 1460420360000L; long april12_2016_midnight_20 = 1460420421903L; @@ -46,12 +44,10 @@ public void works_dates() { long april12_2016_1_20 = 1460424039000L; LessThanOrEqualToMatcher matcher = new LessThanOrEqualToMatcher(april12_2016_midnight_20, DataType.DATETIME); - assertThat(matcher.match(april11_2016_23_59, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_midnight_19, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_midnight_20, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_midnight_20_59, null, null, null), is(true)); - assertThat(matcher.match(april12_2016_1_20, null, null, null), is(false)); + Assert.assertTrue(matcher.match(april11_2016_23_59, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_19, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_20, null, null, null)); + Assert.assertTrue(matcher.match(april12_2016_midnight_20_59, null, null, null)); + Assert.assertFalse(matcher.match(april12_2016_1_20, null, null, null)); } - - -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/matchers/LessThanOrEqualToSemverMatcherTest.java b/client/src/test/java/io/split/engine/matchers/LessThanOrEqualToSemverMatcherTest.java new file mode 100644 index 000000000..349a608ae --- /dev/null +++ b/client/src/test/java/io/split/engine/matchers/LessThanOrEqualToSemverMatcherTest.java @@ -0,0 +1,35 @@ +package io.split.engine.matchers; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +/** + * Tests for EqualToSemverMatcher + */ + +public class LessThanOrEqualToSemverMatcherTest { + + @Test + public void works() { + LessThanOrEqualToSemverMatcher lessThanOrEqualToSemverMatcher = new LessThanOrEqualToSemverMatcher("2.1.8"); + + assertTrue( lessThanOrEqualToSemverMatcher.match("2.1.8", null, null, null)); + assertFalse( lessThanOrEqualToSemverMatcher.match("2.1.9", null, null, null)); + assertTrue( lessThanOrEqualToSemverMatcher.match("2.1.8-rc", null, null, null)); + assertTrue( lessThanOrEqualToSemverMatcher.match("2.0.10", null, null, null)); + assertTrue( lessThanOrEqualToSemverMatcher.match("2.1.8+build", null, null, null)); + assertTrue(lessThanOrEqualToSemverMatcher.equals(lessThanOrEqualToSemverMatcher)); + assertTrue(lessThanOrEqualToSemverMatcher.hashCode() != 0); + } + + @Test + public void testNull() { + LessThanOrEqualToSemverMatcher lessThanOrEqualToSemverMatcher = new LessThanOrEqualToSemverMatcher("2.1.8"); + assertFalse( lessThanOrEqualToSemverMatcher.match(null, null, null, null)); + + lessThanOrEqualToSemverMatcher = new LessThanOrEqualToSemverMatcher("2.ee.8"); + assertFalse(lessThanOrEqualToSemverMatcher.match("2.ee.8", null, null, null)); + } +} diff --git a/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java b/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java index 3aec5ab0d..97e4aebe2 100644 --- a/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java @@ -6,6 +6,7 @@ import io.split.engine.matchers.strings.WhitelistMatcher; import io.split.storages.SegmentCache; import io.split.storages.memory.SegmentCacheInMemoryImpl; +import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -13,9 +14,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Tests for NegatableMatcher. * @@ -24,7 +22,7 @@ public class NegatableMatcherTest { @Test - public void works_all_keys() { + public void worksAllKeys() { AllKeysMatcher delegate = new AllKeysMatcher(); AttributeMatcher.NegatableMatcher matcher = new AttributeMatcher.NegatableMatcher(delegate, true); @@ -32,7 +30,7 @@ public void works_all_keys() { } @Test - public void works_segment() { + public void worksSegment() { SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); segmentCache.updateSegment("foo", Stream.of("a","b").collect(Collectors.toList()), new ArrayList<>(), 1L); UserDefinedSegmentMatcher delegate = new UserDefinedSegmentMatcher("foo"); @@ -44,7 +42,7 @@ public void works_segment() { } @Test - public void works_whitelist() { + public void worksWhitelist() { WhitelistMatcher delegate = new WhitelistMatcher(Lists.newArrayList("a", "b")); AttributeMatcher.NegatableMatcher matcher = new AttributeMatcher.NegatableMatcher(delegate, true); @@ -54,9 +52,8 @@ public void works_whitelist() { } private void test(AttributeMatcher.NegatableMatcher negationMatcher, String key, boolean expected, SegmentCache segmentCache) { - assertThat(negationMatcher.match(key, null, null, new EvaluationContext(Mockito.mock(Evaluator.class), segmentCache)), is(expected)); - assertThat(negationMatcher.delegate().match(key, null, null, new EvaluationContext(Mockito.mock(Evaluator.class), segmentCache)), is(!expected)); - + Assert.assertEquals(expected, negationMatcher.match(key, null, null, new EvaluationContext(Mockito.mock(Evaluator.class), segmentCache))); + Assert.assertNotEquals(expected, negationMatcher.delegate().match(key, null, null, new EvaluationContext(Mockito.mock(Evaluator.class), segmentCache))); } diff --git a/client/src/test/java/io/split/engine/matchers/SemverTest.java b/client/src/test/java/io/split/engine/matchers/SemverTest.java new file mode 100644 index 000000000..40da82643 --- /dev/null +++ b/client/src/test/java/io/split/engine/matchers/SemverTest.java @@ -0,0 +1,112 @@ +package io.split.engine.matchers; + +import org.junit.Test; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertTrue; + +/** + * Tests for AllKeysMatcher + */ +public class SemverTest { + + @Test + public void testValidVersions() throws IOException { + List> versions = new ArrayList<>(); + BufferedReader br = new BufferedReader(new FileReader("src/test/resources/semver/valid-semantic-versions.csv")); + String line; + boolean firstLine = true; + while ((line = br.readLine()) != null) { + if (firstLine) {firstLine = false; continue; } + String[] values = line.split(","); + versions.add(Arrays.asList(values)); + } + for(List version : versions) { + assertTrue(Semver.build(version.get(0)) != null); + assertTrue(Semver.build(version.get(1)) != null); + } + } + + @Test + public void testInvalidVersions() throws IOException { + List> versions = new ArrayList<>(); + BufferedReader br = new BufferedReader(new FileReader("src/test/resources/semver/invalid-semantic-versions.csv")); + String line; + boolean firstLine = true; + while ((line = br.readLine()) != null) { + if (firstLine) {firstLine = false; continue; } + String[] values = line.split(","); + versions.add(Arrays.asList(values)); + } + for(List version : versions) { + assertTrue(Semver.build(version.get(0)) == null); + } + } + + @Test + public void testCompareVersions() throws IOException { + List> versions = new ArrayList<>(); + BufferedReader br = new BufferedReader(new FileReader("src/test/resources/semver/valid-semantic-versions.csv")); + String line; + boolean firstLine = true; + while ((line = br.readLine()) != null) { + if (firstLine) {firstLine = false; continue; } + String[] values = line.split(","); + versions.add(Arrays.asList(values)); + } + for(List version : versions) { + assertTrue(Semver.build(version.get(0)).compare(Semver.build(version.get(1))) == 1); + assertTrue(Semver.build(version.get(1)).compare(Semver.build(version.get(0))) == -1); + } + + versions.clear(); + br = new BufferedReader(new FileReader("src/test/resources/semver/equal-to-semver.csv")); + firstLine = true; + while ((line = br.readLine()) != null) { + if (firstLine) {firstLine = false; continue; } + String[] values = line.split(","); + versions.add(Arrays.asList(values)); + } + for(List version : versions) { + Semver version1 = Semver.build(version.get(0)); + Semver version2 = Semver.build(version.get(1)); + + if (version.get(2).equals("true")) { + assertTrue(version1.version().equals(version2.version())); + } else { + assertTrue(!version1.version().equals(version2.version())); + } + } + + versions.clear(); + br = new BufferedReader(new FileReader("src/test/resources/semver/between-semver.csv")); + firstLine = true; + while ((line = br.readLine()) != null) { + if (firstLine) {firstLine = false; continue; } + String[] values = line.split(","); + versions.add(Arrays.asList(values)); + } + for(List version : versions) { + Semver version1 = Semver.build(version.get(0)); + Semver version2 = Semver.build(version.get(1)); + Semver version3 = Semver.build(version.get(2)); + + if (version.get(3).equals("true")) { + assertTrue(version2.compare(version1) >= 0 && version3.compare(version2) >= 0); + } else { + assertTrue(version2.compare(version1) < 0 || version3.compare(version2) < 0); + } + } + + } + @Test + public void testLeadingZeros() { + assertTrue(Semver.build("1.01.2").version().equals("1\\.1\\.2")); + assertTrue(Semver.build("1.01.2-rc.01").version().equals("1\\.1\\.2-rc\\.1")); + } +} diff --git a/client/src/test/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcherTest.java b/client/src/test/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcherTest.java index caa7b40da..1c84cf2e6 100644 --- a/client/src/test/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcherTest.java @@ -1,5 +1,6 @@ package io.split.engine.matchers.collections; +import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; @@ -7,77 +8,75 @@ import java.util.List; import java.util.Set; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Created by adilaijaz on 4/18/17. */ public class ContainsAllOfSetMatcherTest { @Test - public void works_for_sets() { + public void worksForSets() { Set set = new HashSet<>(); set.add("first"); set.add("second"); ContainsAllOfSetMatcher matcher = new ContainsAllOfSetMatcher(set); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); Set argument = new HashSet<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); } @Test - public void works_for_lists() { + public void worksForLists() { List list = new ArrayList<>(); list.add("first"); list.add("second"); ContainsAllOfSetMatcher matcher = new ContainsAllOfSetMatcher(list); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); List argument = new ArrayList<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); } @Test - public void works_for_empty_paramter() { + public void worksForEmptyParamter() { List list = new ArrayList<>(); ContainsAllOfSetMatcher matcher = new ContainsAllOfSetMatcher(list); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); List argument = new ArrayList<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } } diff --git a/client/src/test/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcherTest.java b/client/src/test/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcherTest.java index 2b54dcbaf..520959aee 100644 --- a/client/src/test/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcherTest.java @@ -1,5 +1,6 @@ package io.split.engine.matchers.collections; +import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; @@ -7,77 +8,74 @@ import java.util.List; import java.util.Set; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Created by adilaijaz on 4/18/17. */ public class ContainsAnyOfSetMatcherTest { @Test - public void works_for_sets() { + public void worksForSets() { Set set = new HashSet<>(); set.add("first"); set.add("second"); ContainsAnyOfSetMatcher matcher = new ContainsAnyOfSetMatcher(set); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); Set argument = new HashSet<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); } @Test - public void works_for_lists() { + public void worksForLists() { List list = new ArrayList<>(); list.add("first"); list.add("second"); ContainsAnyOfSetMatcher matcher = new ContainsAnyOfSetMatcher(list); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); List argument = new ArrayList<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); } @Test - public void works_for_empty_paramter() { + public void worksForEmptyParamter() { List list = new ArrayList<>(); ContainsAnyOfSetMatcher matcher = new ContainsAnyOfSetMatcher(list); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); List argument = new ArrayList<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/matchers/collections/EqualToSetMatcherTest.java b/client/src/test/java/io/split/engine/matchers/collections/EqualToSetMatcherTest.java index 24c783c59..ceb3b4b11 100644 --- a/client/src/test/java/io/split/engine/matchers/collections/EqualToSetMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/collections/EqualToSetMatcherTest.java @@ -1,5 +1,6 @@ package io.split.engine.matchers.collections; +import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; @@ -7,100 +8,97 @@ import java.util.List; import java.util.Set; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Created by adilaijaz on 4/18/17. */ public class EqualToSetMatcherTest { @Test - public void works_for_sets() { + public void worksForSets() { Set set = new HashSet<>(); set.add("first"); set.add("second"); EqualToSetMatcher matcher = new EqualToSetMatcher(set); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); Set argument = new HashSet<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } @Test - public void works_for_sets_same_order() { + public void worksForSetsSameOrder() { Set set = new HashSet<>(); set.add("first"); set.add("second"); EqualToSetMatcher matcher = new EqualToSetMatcher(set); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); Set argument = new HashSet<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } @Test - public void works_for_lists() { + public void worksForLists() { List list = new ArrayList<>(); list.add("first"); list.add("second"); EqualToSetMatcher matcher = new EqualToSetMatcher(list); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); List argument = new ArrayList<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } @Test - public void works_for_empty_paramter() { + public void worksForEmptyParamter() { List list = new ArrayList<>(); EqualToSetMatcher matcher = new EqualToSetMatcher(list); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); List argument = new ArrayList<>(); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } } diff --git a/client/src/test/java/io/split/engine/matchers/collections/PartOfSetMatcherTest.java b/client/src/test/java/io/split/engine/matchers/collections/PartOfSetMatcherTest.java index ff75dc9fb..0a734e884 100644 --- a/client/src/test/java/io/split/engine/matchers/collections/PartOfSetMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/collections/PartOfSetMatcherTest.java @@ -1,5 +1,6 @@ package io.split.engine.matchers.collections; +import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; @@ -7,77 +8,74 @@ import java.util.List; import java.util.Set; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - /** * Created by adilaijaz on 4/18/17. */ public class PartOfSetMatcherTest { @Test - public void works_for_sets() { + public void worksForSets() { Set set = new HashSet<>(); set.add("first"); set.add("second"); PartOfSetMatcher matcher = new PartOfSetMatcher(set); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); Set argument = new HashSet<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } @Test - public void works_for_lists() { + public void worksForLists() { List list = new ArrayList<>(); list.add("first"); list.add("second"); PartOfSetMatcher matcher = new PartOfSetMatcher(list); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); List argument = new ArrayList<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(true)); + Assert.assertTrue(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } @Test - public void works_for_empty_paramter() { + public void worksForEmptyParamter() { List list = new ArrayList<>(); PartOfSetMatcher matcher = new PartOfSetMatcher(list); - assertThat(matcher.match(null, null, null, null), is(false)); + Assert.assertFalse(matcher.match(null, null, null, null)); List argument = new ArrayList<>(); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("second"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("first"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); argument.add("third"); - assertThat(matcher.match(argument, null, null, null), is(false)); + Assert.assertFalse(matcher.match(argument, null, null, null)); } } diff --git a/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java index ce263bfe4..0872fb45b 100644 --- a/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java @@ -36,18 +36,17 @@ public class SegmentFetcherImpTest { private static final TelemetryStorage TELEMETRY_STORAGE = Mockito.mock(InMemoryTelemetryStorage.class); @Test - public void works_when_we_start_without_state() throws InterruptedException { + public void worksWhenWeStartWithoutState() throws InterruptedException { works(-1L); } @Test - public void works_when_we_start_with_state() throws InterruptedException { + public void worksWhenWeStartWithState() throws InterruptedException { works(20L); } @Test - public void works_when_there_are_no_changes() throws InterruptedException { - long startingChangeNumber = -1L; + public void worksWhenThereAreNoChanges() throws InterruptedException { SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); @@ -112,13 +111,13 @@ private void works(long startingChangeNumber) throws InterruptedException { @Test(expected = NullPointerException.class) - public void does_not_work_if_segment_change_fetcher_is_null() { + public void doesNotWorkIfSegmentChangeFetcherIsNull() { SegmentCacheProducer segmentCacheProducer = Mockito.mock(SegmentCacheProducer.class); SegmentFetcher fetcher = new SegmentFetcherImp(SEGMENT_NAME, null, segmentCacheProducer, TELEMETRY_STORAGE); } @Test(expected = NullPointerException.class) - public void does_not_work_if_segment_name_is_null() { + public void doesNotWorkIfSegmentNameIsNull() { SegmentCacheProducer segmentCacheProducer = Mockito.mock(SegmentCacheProducer.class); SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentFetcher fetcher = new SegmentFetcherImp(null, segmentChangeFetcher, segmentCacheProducer, TELEMETRY_STORAGE); @@ -176,4 +175,4 @@ private SegmentChange getSegmentChange(long since, long till){ segmentChange.removed = new ArrayList<>(); return segmentChange; } -} +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/splitter/HashConsistencyTest.java b/client/src/test/java/io/split/engine/splitter/HashConsistencyTest.java index 34b0e557d..cb2200294 100644 --- a/client/src/test/java/io/split/engine/splitter/HashConsistencyTest.java +++ b/client/src/test/java/io/split/engine/splitter/HashConsistencyTest.java @@ -12,7 +12,6 @@ import java.io.FileReader; import java.io.IOException; import java.net.URL; -import java.nio.charset.Charset; public class HashConsistencyTest { @Test @@ -77,7 +76,7 @@ private void validateFileLegacyHash(File file) throws IOException { int expected_hash = Integer.parseInt(parts[2]); int expected_bucket = Integer.parseInt(parts[3]); - int hash = Splitter.legacy_hash(key, seed); + int hash = Splitter.legacyHash(key, seed); int bucket = Splitter.bucket(hash); Assert.assertEquals(expected_hash, hash); @@ -98,7 +97,7 @@ private void validateFileMurmur3Hash(File file) throws IOException { long expected_hash = Long.parseLong(parts[2]); int expected_bucket = Integer.parseInt(parts[3]); - long hash = Splitter.murmur_hash(key, seed); + long hash = Splitter.murmurHash(key, seed); int bucket = Splitter.bucket(hash); Assert.assertEquals(expected_hash, hash); diff --git a/client/src/test/java/io/split/engine/sse/AuthApiClientTest.java b/client/src/test/java/io/split/engine/sse/AuthApiClientTest.java index dab743602..b6f05e04c 100644 --- a/client/src/test/java/io/split/engine/sse/AuthApiClientTest.java +++ b/client/src/test/java/io/split/engine/sse/AuthApiClientTest.java @@ -1,7 +1,11 @@ package io.split.engine.sse; import io.split.TestHelper; +import io.split.client.RequestDecorator; +import io.split.client.utils.SDKMetadata; import io.split.engine.sse.dtos.AuthenticationResponse; +import io.split.service.SplitHttpClient; +import io.split.service.SplitHttpClientImpl; import io.split.telemetry.storage.InMemoryTelemetryStorage; import io.split.telemetry.storage.TelemetryStorage; import org.apache.commons.lang3.StringUtils; @@ -14,6 +18,7 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; public class AuthApiClientTest { private static TelemetryStorage TELEMETRY_STORAGE = Mockito.mock(InMemoryTelemetryStorage.class); @@ -22,11 +27,15 @@ public class AuthApiClientTest { public void setUp() { TELEMETRY_STORAGE = Mockito.mock(InMemoryTelemetryStorage.class); } - @Test - public void authenticateWithPushEnabledShouldReturnSuccess() throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("streaming-auth-push-enabled.json", HttpStatus.SC_OK); - AuthApiClient authApiClient = new AuthApiClientImp( "www.split-test.io", httpClientMock, TELEMETRY_STORAGE); + @Test + public void authenticateWithPushEnabledShouldReturnSuccess() throws IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException, URISyntaxException { + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("streaming-auth-push-enabled.json", + HttpStatus.SC_OK); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", splitHttpClient, TELEMETRY_STORAGE); AuthenticationResponse result = authApiClient.Authenticate(); Assert.assertTrue(result.isPushEnabled()); @@ -36,15 +45,20 @@ public void authenticateWithPushEnabledShouldReturnSuccess() throws IOException, Assert.assertTrue(result.getExpiration() > 0); Mockito.verify(TELEMETRY_STORAGE, Mockito.times(1)).recordTokenRefreshes(); Mockito.verify(TELEMETRY_STORAGE, Mockito.times(1)).recordSyncLatency(Mockito.anyObject(), Mockito.anyLong()); - Mockito.verify(TELEMETRY_STORAGE, Mockito.times(1)).recordSuccessfulSync(Mockito.anyObject(), Mockito.anyLong()); + Mockito.verify(TELEMETRY_STORAGE, Mockito.times(1)).recordSuccessfulSync(Mockito.anyObject(), + Mockito.anyLong()); } @Test - public void authenticateWithPushEnabledWithWrongTokenShouldReturnError() throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("streaming-auth-push-enabled-wrong-token.json", HttpStatus.SC_OK); - - AuthApiClient authApiClient = new AuthApiClientImp( "www.split-test.io", httpClientMock, TELEMETRY_STORAGE); + public void authenticateWithPushEnabledWithWrongTokenShouldReturnError() throws IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException, URISyntaxException { + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("streaming-auth-push-enabled-wrong-token.json", + HttpStatus.SC_OK); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + + AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", splitHttpClient, TELEMETRY_STORAGE); AuthenticationResponse result = authApiClient.Authenticate(); Assert.assertFalse(result.isPushEnabled()); @@ -55,10 +69,31 @@ public void authenticateWithPushEnabledWithWrongTokenShouldReturnError() throws } @Test - public void authenticateWithPushDisabledShouldReturnSuccess() throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("streaming-auth-push-disabled.json", HttpStatus.SC_OK); + public void authenticateWithPushDisabledShouldReturnSuccess() throws IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException, URISyntaxException { + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("streaming-auth-push-disabled.json", + HttpStatus.SC_OK); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + + AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", splitHttpClient, TELEMETRY_STORAGE); + AuthenticationResponse result = authApiClient.Authenticate(); + + Assert.assertFalse(result.isPushEnabled()); + Assert.assertTrue(StringUtils.isEmpty(result.getChannels())); + Assert.assertFalse(result.isRetry()); + Assert.assertTrue(StringUtils.isEmpty(result.getToken())); + } - AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", httpClientMock, TELEMETRY_STORAGE); + @Test + public void authenticateWithPushDisabledWithEmptyTokenShouldReturnSuccess() throws IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException, URISyntaxException { + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("streaming-auth-push-disabled-empty-token.json", + HttpStatus.SC_OK); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + + AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", splitHttpClient, TELEMETRY_STORAGE); AuthenticationResponse result = authApiClient.Authenticate(); Assert.assertFalse(result.isPushEnabled()); @@ -68,10 +103,13 @@ public void authenticateWithPushDisabledShouldReturnSuccess() throws IOException } @Test - public void authenticateServerErrorShouldReturnErrorWithRetry() throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + public void authenticateServerErrorShouldReturnErrorWithRetry() throws IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException, URISyntaxException { CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("", HttpStatus.SC_INTERNAL_SERVER_ERROR); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); - AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", httpClientMock, TELEMETRY_STORAGE); + AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", splitHttpClient, TELEMETRY_STORAGE); AuthenticationResponse result = authApiClient.Authenticate(); Assert.assertFalse(result.isPushEnabled()); @@ -81,10 +119,13 @@ public void authenticateServerErrorShouldReturnErrorWithRetry() throws IOExcepti } @Test - public void authenticateServerBadRequestShouldReturnErrorWithoutRetry() throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + public void authenticateServerBadRequestShouldReturnErrorWithoutRetry() throws IOException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException, URISyntaxException { CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("", HttpStatus.SC_BAD_REQUEST); - AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", httpClientMock, TELEMETRY_STORAGE); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); + AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", splitHttpClient, TELEMETRY_STORAGE); AuthenticationResponse result = authApiClient.Authenticate(); Assert.assertFalse(result.isPushEnabled()); @@ -94,10 +135,13 @@ public void authenticateServerBadRequestShouldReturnErrorWithoutRetry() throws I } @Test - public void authenticateServerUnauthorizedShouldReturnErrorWithoutRetry() throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + public void authenticateServerUnauthorizedShouldReturnErrorWithoutRetry() throws IOException, + IllegalAccessException, NoSuchMethodException, InvocationTargetException, URISyntaxException { CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("", HttpStatus.SC_UNAUTHORIZED); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClientMock, new RequestDecorator(null), + "qwerty", metadata()); - AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", httpClientMock, TELEMETRY_STORAGE); + AuthApiClient authApiClient = new AuthApiClientImp("www.split-test.io", splitHttpClient, TELEMETRY_STORAGE); AuthenticationResponse result = authApiClient.Authenticate(); Assert.assertFalse(result.isPushEnabled()); @@ -106,4 +150,9 @@ public void authenticateServerUnauthorizedShouldReturnErrorWithoutRetry() throws Assert.assertFalse(result.isRetry()); Mockito.verify(TELEMETRY_STORAGE, Mockito.times(1)).recordAuthRejections(); } + + private SDKMetadata metadata() { + return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); + } + } diff --git a/client/src/test/java/io/split/engine/sse/EventSourceClientTest.java b/client/src/test/java/io/split/engine/sse/EventSourceClientTest.java index c5bc22b1b..604b6371f 100644 --- a/client/src/test/java/io/split/engine/sse/EventSourceClientTest.java +++ b/client/src/test/java/io/split/engine/sse/EventSourceClientTest.java @@ -1,6 +1,7 @@ package io.split.engine.sse; import io.split.SSEMockServer; +import io.split.client.RequestDecorator; import io.split.engine.sse.client.SSEClient; import io.split.engine.sse.dtos.ErrorNotification; import io.split.engine.sse.dtos.FeatureFlagChangeNotification; @@ -42,7 +43,7 @@ public void startShouldConnect() throws IOException { TelemetryRuntimeProducer telemetryRuntimeProducer = Mockito.mock(InMemoryTelemetryStorage.class); sseServer.start(); - EventSourceClient eventSourceClient = new EventSourceClientImp("http://localhost:" + sseServer.getPort(), _notificationParser, _notificationProcessor, _pushStatusTracker, buildHttpClient(), telemetryRuntimeProducer, null); + EventSourceClient eventSourceClient = new EventSourceClientImp("http://localhost:" + sseServer.getPort(), _notificationParser, _notificationProcessor, _pushStatusTracker, buildHttpClient(), telemetryRuntimeProducer, null, new RequestDecorator(null)); boolean result = eventSourceClient.start("channel-test", "token-test"); @@ -57,7 +58,7 @@ public void startShouldReconnect() throws IOException { SSEMockServer sseServer = buildSSEMockServer(eventQueue); TelemetryRuntimeProducer telemetryRuntimeProducer = Mockito.mock(InMemoryTelemetryStorage.class); sseServer.start(); - EventSourceClient eventSourceClient = new EventSourceClientImp("http://fake:" + sseServer.getPort(), _notificationParser, _notificationProcessor, _pushStatusTracker, buildHttpClient(), telemetryRuntimeProducer, null); + EventSourceClient eventSourceClient = new EventSourceClientImp("http://fake:" + sseServer.getPort(), _notificationParser, _notificationProcessor, _pushStatusTracker, buildHttpClient(), telemetryRuntimeProducer, null, new RequestDecorator(null)); boolean result = eventSourceClient.start("channel-test", "token-test"); @@ -74,7 +75,7 @@ public void startAndReceiveNotification() throws IOException { SSEMockServer sseServer = buildSSEMockServer(eventQueue); TelemetryRuntimeProducer telemetryRuntimeProducer = Mockito.mock(InMemoryTelemetryStorage.class); sseServer.start(); - EventSourceClient eventSourceClient = new EventSourceClientImp("http://localhost:" + sseServer.getPort(), _notificationParser, _notificationProcessor, _pushStatusTracker, buildHttpClient(), telemetryRuntimeProducer, null); + EventSourceClient eventSourceClient = new EventSourceClientImp("http://localhost:" + sseServer.getPort(), _notificationParser, _notificationProcessor, _pushStatusTracker, buildHttpClient(), telemetryRuntimeProducer, null, new RequestDecorator(null)); boolean result = eventSourceClient.start("channel-test", "token-test"); diff --git a/client/src/test/java/io/split/engine/sse/SSEClientTest.java b/client/src/test/java/io/split/engine/sse/SSEClientTest.java index 97aaa4de5..15f13d3b3 100644 --- a/client/src/test/java/io/split/engine/sse/SSEClientTest.java +++ b/client/src/test/java/io/split/engine/sse/SSEClientTest.java @@ -1,5 +1,6 @@ package io.split.engine.sse; +import io.split.client.RequestDecorator; import io.split.engine.sse.client.SSEClient; import io.split.telemetry.storage.InMemoryTelemetryStorage; import io.split.telemetry.storage.TelemetryRuntimeProducer; @@ -38,7 +39,7 @@ public void basicUsageTest() throws URISyntaxException, InterruptedException { CloseableHttpClient httpClient = httpClientbuilder.build(); SSEClient sse = new SSEClient(e -> null, - s -> null, httpClient, telemetryRuntimeProducer, null); + s -> null, httpClient, telemetryRuntimeProducer, null, new RequestDecorator(null)); sse.open(uri); Thread.sleep(5000); sse.close(); diff --git a/client/src/test/java/io/split/service/HttpPostImpTest.java b/client/src/test/java/io/split/service/HttpPostImpTest.java index b6b422d89..9efdb21cb 100644 --- a/client/src/test/java/io/split/service/HttpPostImpTest.java +++ b/client/src/test/java/io/split/service/HttpPostImpTest.java @@ -1,6 +1,8 @@ package io.split.service; import io.split.TestHelper; +import io.split.client.RequestDecorator; +import io.split.client.utils.SDKMetadata; import io.split.telemetry.domain.enums.HttpParamsWrapper; import io.split.telemetry.storage.InMemoryTelemetryStorage; import io.split.telemetry.storage.TelemetryStorage; @@ -13,30 +15,41 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URI; +import java.net.URISyntaxException; -public class HttpPostImpTest{ +public class HttpPostImpTest { private static final String URL = "www.split.io"; @Test - public void testPostWith200() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException { - CloseableHttpClient client =TestHelper.mockHttpClient(URL, HttpStatus.SC_OK); + public void testPostWith200() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, + IOException, URISyntaxException { + CloseableHttpClient client = TestHelper.mockHttpClient(URL, HttpStatus.SC_OK); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(client, new RequestDecorator(null), "qwerty", + metadata()); TelemetryStorage telemetryStorage = new InMemoryTelemetryStorage(); - HttpPostImp httpPostImp = new HttpPostImp(client, telemetryStorage); + HttpPostImp httpPostImp = new HttpPostImp(splitHttpClient, telemetryStorage); httpPostImp.post(URI.create(URL), new Object(), "Metrics", HttpParamsWrapper.TELEMETRY); Mockito.verify(client, Mockito.times(1)).execute(Mockito.any()); - Assert.assertNotEquals(0, telemetryStorage.getLastSynchronization().get_telemetry()); - Assert.assertEquals(1, telemetryStorage.popHTTPLatencies().get_telemetry().stream().mapToInt(Long::intValue).sum()); + Assert.assertNotEquals(0, telemetryStorage.getLastSynchronization().getTelemetry()); + Assert.assertEquals(1, telemetryStorage.popHTTPLatencies().getTelemetry().stream().mapToInt(Long::intValue).sum()); } @Test - public void testPostWith400() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException { - CloseableHttpClient client =TestHelper.mockHttpClient(URL, HttpStatus.SC_CLIENT_ERROR); + public void testPostWith400() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, + IOException, URISyntaxException { + CloseableHttpClient client = TestHelper.mockHttpClient(URL, HttpStatus.SC_CLIENT_ERROR); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(client, new RequestDecorator(null), "qwerty", + metadata()); TelemetryStorage telemetryStorage = new InMemoryTelemetryStorage(); - HttpPostImp httpPostImp = new HttpPostImp(client, telemetryStorage); + HttpPostImp httpPostImp = new HttpPostImp(splitHttpClient, telemetryStorage); httpPostImp.post(URI.create(URL), new Object(), "Metrics", HttpParamsWrapper.TELEMETRY); Mockito.verify(client, Mockito.times(1)).execute(Mockito.any()); + Assert.assertEquals(1, telemetryStorage.popHTTPErrors().getTelemetry().get(Long.valueOf(HttpStatus.SC_CLIENT_ERROR)).intValue()); + } - Assert.assertEquals(1, telemetryStorage.popHTTPErrors().get_telemetry().get(Long.valueOf(HttpStatus.SC_CLIENT_ERROR)).intValue()); + private SDKMetadata metadata() { + return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); } -} \ No newline at end of file + +} diff --git a/client/src/test/java/io/split/service/HttpSplitClientTest.java b/client/src/test/java/io/split/service/HttpSplitClientTest.java new file mode 100644 index 000000000..946775f39 --- /dev/null +++ b/client/src/test/java/io/split/service/HttpSplitClientTest.java @@ -0,0 +1,175 @@ +package io.split.service; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +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 io.split.service.SplitHttpClient; +import io.split.service.SplitHttpClientImpl; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; +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.hamcrest.core.IsInstanceOf.instanceOf; +import static org.mockito.Mockito.verify; + +public class HttpSplitClientTest { + + @Test + public void testGetWithSpecialCharacters() throws URISyntaxException, InvocationTargetException, + NoSuchMethodException, IllegalAccessException, IOException { + URI rootTarget = URI.create("https://api.split.io/splitChanges?since=1234567"); + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("split-change-special-characters.json", + HttpStatus.SC_OK); + RequestDecorator decorator = new RequestDecorator(null); + + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, decorator, "qwerty", metadata()); + Map> additionalHeaders = Collections.singletonMap("AdditionalHeader", + Collections.singletonList("add")); + + SplitHttpResponse splitHttpResponse = splitHtpClient.get(rootTarget, + new FetchOptions.Builder().cacheControlHeaders(true).build(), additionalHeaders); + SplitChange change = Json.fromJson(splitHttpResponse.body(), SplitChange.class); + + ArgumentCaptor captor = ArgumentCaptor.forClass(HttpUriRequest.class); + verify(httpClientMock).execute(captor.capture()); + HttpUriRequest request = captor.getValue(); + assertThat(request.getFirstHeader("AdditionalHeader").getValue(), is(equalTo("add"))); + + Header[] headers = splitHttpResponse.responseHeaders(); + assertThat(headers[0].getName(), is(equalTo("Via"))); + assertThat(headers[0].getValue(), is(equalTo("HTTP/1.1 m_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 testGetError() throws URISyntaxException, InvocationTargetException, NoSuchMethodException, + IllegalAccessException, IOException { + URI rootTarget = URI.create("https://api.split.io/splitChanges?since=1234567"); + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("split-change-special-characters.json", + HttpStatus.SC_INTERNAL_SERVER_ERROR); + RequestDecorator decorator = new RequestDecorator(null); + + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, decorator, "qwerty", metadata()); + SplitHttpResponse splitHttpResponse = splitHtpClient.get(rootTarget, + 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 { + URI rootTarget = URI.create("https://api.split.io/splitChanges?since=1234567"); + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("split-change-special-characters.json", + HttpStatus.SC_INTERNAL_SERVER_ERROR); + RequestDecorator decorator = null; + + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, decorator, "qwerty", metadata()); + splitHtpClient.get(rootTarget, + new FetchOptions.Builder().cacheControlHeaders(true).build(), null); + } + + @Test + public void testPost() throws URISyntaxException, IOException, IllegalAccessException, NoSuchMethodException, + InvocationTargetException { + URI rootTarget = URI.create("https://kubernetesturl.com/split/api/testImpressions/bulk"); + + // Setup response mock + CloseableHttpClient httpClient = TestHelper.mockHttpClient("", HttpStatus.SC_OK); + RequestDecorator decorator = new RequestDecorator(null); + + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClient, 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")); + SplitHttpResponse splitHttpResponse = splitHtpClient.post(rootTarget, Utils.toJsonEntity(toSend), + additionalHeaders); + + // Capture outgoing request and validate it + ArgumentCaptor captor = ArgumentCaptor.forClass(HttpUriRequest.class); + verify(httpClient).execute(captor.capture()); + HttpUriRequest request = captor.getValue(); + assertThat(request.getUri(), + is(equalTo(URI.create("https://kubernetesturl.com/split/api/testImpressions/bulk")))); + assertThat(request.getFirstHeader("SplitSDKImpressionsMode").getValue(), is(equalTo("OPTIMIZED"))); + assertThat(request, instanceOf(HttpPost.class)); + HttpPost asPostRequest = (HttpPost) request; + InputStreamReader reader = new InputStreamReader(asPostRequest.getEntity().getContent()); + Gson gson = new Gson(); + List payload = gson.fromJson(reader, new TypeToken>() { + }.getType()); + assertThat(payload.size(), is(equalTo(2))); + Assert.assertEquals(200, (long) splitHttpResponse.statusCode()); + } + + @Test + public void testPosttNoExceptionOnHttpErrorCode() throws URISyntaxException, InvocationTargetException, + NoSuchMethodException, IllegalAccessException, IOException { + URI rootTarget = URI.create("https://api.split.io/splitChanges?since=1234567"); + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("split-change-special-characters.json", + HttpStatus.SC_INTERNAL_SERVER_ERROR); + RequestDecorator decorator = new RequestDecorator(null); + + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, decorator, "qwerty", metadata()); + SplitHttpResponse splitHttpResponse = splitHtpClient.post(rootTarget, + Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null); + Assert.assertEquals(500, (long) splitHttpResponse.statusCode()); + + } + + @Test(expected = IOException.class) + public void testPosttException() throws URISyntaxException, InvocationTargetException, NoSuchMethodException, + IllegalAccessException, IOException { + URI rootTarget = URI.create("https://api.split.io/splitChanges?since=1234567"); + CloseableHttpClient httpClientMock = TestHelper.mockHttpClient("split-change-special-characters.json", + HttpStatus.SC_INTERNAL_SERVER_ERROR); + + SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, null, "qwerty", metadata()); + splitHtpClient.post(rootTarget, 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/client/src/test/java/io/split/storages/pluggable/CustomStorageWrapperImp.java b/client/src/test/java/io/split/storages/pluggable/CustomStorageWrapperImp.java index 6b936254e..65ca18b06 100644 --- a/client/src/test/java/io/split/storages/pluggable/CustomStorageWrapperImp.java +++ b/client/src/test/java/io/split/storages/pluggable/CustomStorageWrapperImp.java @@ -285,7 +285,7 @@ public ConcurrentMap getLatencies() { return _latencies; } - public ConcurrentMap get_impressionsCount() { + public ConcurrentMap getImpressionsCount() { return _impressionsCount; } diff --git a/client/src/test/java/io/split/telemetry/storage/InMemoryTelemetryStorageTest.java b/client/src/test/java/io/split/telemetry/storage/InMemoryTelemetryStorageTest.java index 3b8a0941c..a74ee03ac 100644 --- a/client/src/test/java/io/split/telemetry/storage/InMemoryTelemetryStorageTest.java +++ b/client/src/test/java/io/split/telemetry/storage/InMemoryTelemetryStorageTest.java @@ -71,22 +71,22 @@ public void testInMemoryTelemetryStorage() { HTTPLatencies httpLatencies = telemetryStorage.popHTTPLatencies(); - Assert.assertEquals(3, httpLatencies.get_splits().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(2, httpLatencies.get_telemetry().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(2, httpLatencies.get_events().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, httpLatencies.get_segments().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, httpLatencies.get_impressions().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, httpLatencies.get_impressionsCount().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(0, httpLatencies.get_token().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(3, httpLatencies.getSplits().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(2, httpLatencies.getTelemetry().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(2, httpLatencies.getEvents().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, httpLatencies.getSegments().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, httpLatencies.getImpressions().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, httpLatencies.getImpressionsCount().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, httpLatencies.getToken().stream().mapToInt(Long::intValue).sum()); httpLatencies = telemetryStorage.popHTTPLatencies(); - Assert.assertEquals(0, httpLatencies.get_splits().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(0, httpLatencies.get_telemetry().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(0, httpLatencies.get_events().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(0, httpLatencies.get_segments().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(0, httpLatencies.get_impressions().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(0, httpLatencies.get_impressionsCount().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(0, httpLatencies.get_token().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, httpLatencies.getSplits().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, httpLatencies.getTelemetry().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, httpLatencies.getEvents().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, httpLatencies.getSegments().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, httpLatencies.getImpressions().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, httpLatencies.getImpressionsCount().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, httpLatencies.getToken().stream().mapToInt(Long::intValue).sum()); //Exceptions @@ -191,13 +191,13 @@ public void testInMemoryTelemetryStorage() { telemetryStorage.recordSuccessfulSync(LastSynchronizationRecordsEnum.TOKEN, 129); LastSynchronization lastSynchronization = telemetryStorage.getLastSynchronization(); - Assert.assertEquals(800, lastSynchronization.get_events()); - Assert.assertEquals(129, lastSynchronization.get_token()); - Assert.assertEquals(1580, lastSynchronization.get_segments()); - Assert.assertEquals(0, lastSynchronization.get_splits()); - Assert.assertEquals(10500, lastSynchronization.get_impressions()); - Assert.assertEquals(1500, lastSynchronization.get_impressionsCount()); - Assert.assertEquals(265, lastSynchronization.get_telemetry()); + Assert.assertEquals(800, lastSynchronization.getEvents()); + Assert.assertEquals(129, lastSynchronization.getToken()); + Assert.assertEquals(1580, lastSynchronization.getSegments()); + Assert.assertEquals(0, lastSynchronization.getSplits()); + Assert.assertEquals(10500, lastSynchronization.getImpressions()); + Assert.assertEquals(1500, lastSynchronization.getImpressionsCount()); + Assert.assertEquals(265, lastSynchronization.getTelemetry()); //Session length telemetryStorage.recordSessionLength(91218); @@ -216,21 +216,21 @@ public void testInMemoryTelemetryStorage() { telemetryStorage.recordSyncError(ResourceEnum.TOKEN_SYNC, 403); HTTPErrors httpErrors = telemetryStorage.popHTTPErrors(); - Assert.assertEquals(2, httpErrors.get_telemetry().get(400l).intValue()); - Assert.assertEquals(1, httpErrors.get_segments().get(501l).intValue()); - Assert.assertEquals(2, httpErrors.get_impressions().get(403l).intValue()); - Assert.assertEquals(1, httpErrors.get_impressionsCount().get(403l).intValue()); - Assert.assertEquals(1, httpErrors.get_events().get(503l).intValue()); - Assert.assertEquals(1, httpErrors.get_splits().get(403l).intValue()); - Assert.assertEquals(1, httpErrors.get_token().get(403l).intValue()); + Assert.assertEquals(2, httpErrors.getTelemetry().get(400l).intValue()); + Assert.assertEquals(1, httpErrors.getSegments().get(501l).intValue()); + Assert.assertEquals(2, httpErrors.getImpressions().get(403l).intValue()); + Assert.assertEquals(1, httpErrors.getImpressionsCount().get(403l).intValue()); + Assert.assertEquals(1, httpErrors.getEvents().get(503l).intValue()); + Assert.assertEquals(1, httpErrors.getSplits().get(403l).intValue()); + Assert.assertEquals(1, httpErrors.getToken().get(403l).intValue()); //Streaming events StreamingEvent streamingEvent = new StreamingEvent(1, 290, 91218); telemetryStorage.recordStreamingEvents(streamingEvent); List streamingEvents = telemetryStorage.popStreamingEvents(); - Assert.assertEquals(290, streamingEvents.get(0).get_data()); - Assert.assertEquals(1, streamingEvents.get(0).get_type()); + Assert.assertEquals(290, streamingEvents.get(0).getData()); + Assert.assertEquals(1, streamingEvents.get(0).getType()); Assert.assertEquals(91218, streamingEvents.get(0).getTimestamp()); //Check list has been cleared diff --git a/client/src/test/java/io/split/telemetry/synchronizer/TelemetryInMemorySubmitterTest.java b/client/src/test/java/io/split/telemetry/synchronizer/TelemetryInMemorySubmitterTest.java index 10aa46298..39a6be14c 100644 --- a/client/src/test/java/io/split/telemetry/synchronizer/TelemetryInMemorySubmitterTest.java +++ b/client/src/test/java/io/split/telemetry/synchronizer/TelemetryInMemorySubmitterTest.java @@ -1,7 +1,11 @@ package io.split.telemetry.synchronizer; import io.split.TestHelper; +import io.split.client.RequestDecorator; import io.split.client.dtos.UniqueKeys; +import io.split.client.utils.SDKMetadata; +import io.split.service.SplitHttpClient; +import io.split.service.SplitHttpClientImpl; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SplitCacheConsumer; import io.split.client.ApiKeyCounter; @@ -44,20 +48,25 @@ public class TelemetryInMemorySubmitterTest { public static final String TELEMETRY_ENDPOINT = "https://telemetry.split.io/api/v1"; @Test - public void testSynchronizeConfig() throws URISyntaxException, NoSuchMethodException, IOException, IllegalAccessException, InvocationTargetException { + public void testSynchronizeConfig() throws URISyntaxException, NoSuchMethodException, IOException, + IllegalAccessException, InvocationTargetException { CloseableHttpClient httpClient = TestHelper.mockHttpClient(TELEMETRY_ENDPOINT, HttpStatus.SC_OK); - TelemetrySynchronizer telemetrySynchronizer = getTelemetrySynchronizer(httpClient); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + TelemetrySynchronizer telemetrySynchronizer = getTelemetrySynchronizer(splitHttpClient); SplitClientConfig splitClientConfig = SplitClientConfig.builder().build(); - telemetrySynchronizer.synchronizeConfig(splitClientConfig, 100l, new HashMap(), new ArrayList()); + telemetrySynchronizer.synchronizeConfig(splitClientConfig, 100l, new HashMap(), + new ArrayList()); Mockito.verify(httpClient, Mockito.times(1)).execute(Mockito.any()); } - @Test public void testSynchronizeStats() throws Exception { CloseableHttpClient httpClient = TestHelper.mockHttpClient(TELEMETRY_ENDPOINT, HttpStatus.SC_OK); - TelemetrySynchronizer telemetrySynchronizer = getTelemetrySynchronizer(httpClient); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + TelemetrySynchronizer telemetrySynchronizer = getTelemetrySynchronizer(splitHttpClient); telemetrySynchronizer.synchronizeStats(); Mockito.verify(httpClient, Mockito.times(1)).execute(Mockito.any()); @@ -66,7 +75,9 @@ public void testSynchronizeStats() throws Exception { @Test public void testSynchronizeUniqueKeys() throws Exception { CloseableHttpClient httpClient = TestHelper.mockHttpClient(TELEMETRY_ENDPOINT, HttpStatus.SC_OK); - TelemetrySynchronizer telemetrySynchronizer = getTelemetrySynchronizer(httpClient); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + TelemetrySynchronizer telemetrySynchronizer = getTelemetrySynchronizer(splitHttpClient); List keys = new ArrayList<>(); keys.add("key-1"); @@ -80,7 +91,8 @@ public void testSynchronizeUniqueKeys() throws Exception { } @Test - public void testConfig() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException, URISyntaxException, NoSuchFieldException { + public void testConfig() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, + IOException, URISyntaxException, NoSuchFieldException { ApiKeyCounter.getApiKeyCounterInstance().clearApiKeys(); ApiKeyCounter.getApiKeyCounterInstance().add(FIRST_KEY); ApiKeyCounter.getApiKeyCounterInstance().add(FIRST_KEY); @@ -89,8 +101,11 @@ public void testConfig() throws InvocationTargetException, NoSuchMethodException ApiKeyCounter.getApiKeyCounterInstance().add(SECOND_KEY); TelemetryStorage telemetryStorage = new InMemoryTelemetryStorage(); CloseableHttpClient httpClient = TestHelper.mockHttpClient(TELEMETRY_ENDPOINT, HttpStatus.SC_OK); - TelemetryInMemorySubmitter telemetrySynchronizer = getTelemetrySynchronizer(httpClient); - SplitClientConfig splitClientConfig = SplitClientConfig.builder().flagSetsFilter(Arrays.asList("a", "_b", "a", "a", "c", "d", "_d")).build(); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + TelemetryInMemorySubmitter telemetrySynchronizer = getTelemetrySynchronizer(splitHttpClient); + SplitClientConfig splitClientConfig = SplitClientConfig.builder() + .flagSetsFilter(Arrays.asList("a", "_b", "a", "a", "c", "d", "_d")).build(); populateConfig(telemetryStorage); Field telemetryStorageConsumer = TelemetryInMemorySubmitter.class.getDeclaredField("_telemetryStorageConsumer"); telemetryStorageConsumer.setAccessible(true); @@ -98,7 +113,8 @@ public void testConfig() throws InvocationTargetException, NoSuchMethodException modifiersField.setAccessible(true); modifiersField.setInt(telemetryStorageConsumer, telemetryStorageConsumer.getModifiers() & ~Modifier.FINAL); telemetryStorageConsumer.set(telemetrySynchronizer, telemetryStorage); - Config config = telemetrySynchronizer.generateConfig(splitClientConfig, 100l, ApiKeyCounter.getApiKeyCounterInstance().getFactoryInstances(), new ArrayList<>()); + Config config = telemetrySynchronizer.generateConfig(splitClientConfig, 100l, + ApiKeyCounter.getApiKeyCounterInstance().getFactoryInstances(), new ArrayList<>()); Assert.assertEquals(3, config.getRedundantFactories()); Assert.assertEquals(2, config.getBurTimeouts()); Assert.assertEquals(3, config.getNonReadyUsages()); @@ -110,7 +126,9 @@ public void testConfig() throws InvocationTargetException, NoSuchMethodException public void testStats() throws Exception { TelemetryStorage telemetryStorage = new InMemoryTelemetryStorage(); CloseableHttpClient httpClient = TestHelper.mockHttpClient(TELEMETRY_ENDPOINT, HttpStatus.SC_OK); - TelemetryInMemorySubmitter telemetrySynchronizer = getTelemetrySynchronizer(httpClient); + SplitHttpClient splitHttpClient = SplitHttpClientImpl.create(httpClient, new RequestDecorator(null), "qwerty", + metadata()); + TelemetryInMemorySubmitter telemetrySynchronizer = getTelemetrySynchronizer(splitHttpClient); populateStats(telemetryStorage); Field telemetryStorageConsumer = TelemetryInMemorySubmitter.class.getDeclaredField("_telemetryStorageConsumer"); telemetryStorageConsumer.setAccessible(true); @@ -122,20 +140,26 @@ public void testStats() throws Exception { Stats stats = telemetrySynchronizer.generateStats(); Assert.assertEquals(2, stats.getMethodLatencies().getTreatment().stream().mapToInt(Long::intValue).sum()); Assert.assertEquals(2, stats.getMethodLatencies().getTreatments().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getMethodLatencies().getTreatmentsWithConfig().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getMethodLatencies().getTreatmentWithConfig().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getMethodLatencies().getTreatmentByFlagSet().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getMethodLatencies().getTreatmentByFlagSets().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getMethodLatencies().getTreatmentWithConfigByFlagSet().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getMethodLatencies().getTreatmentWithConfigByFlagSets().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, + stats.getMethodLatencies().getTreatmentsWithConfig().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, + stats.getMethodLatencies().getTreatmentWithConfig().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, + stats.getMethodLatencies().getTreatmentByFlagSet().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, + stats.getMethodLatencies().getTreatmentByFlagSets().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, + stats.getMethodLatencies().getTreatmentWithConfigByFlagSet().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, + stats.getMethodLatencies().getTreatmentWithConfigByFlagSets().stream().mapToInt(Long::intValue).sum()); Assert.assertEquals(0, stats.getMethodLatencies().getTrack().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(3, stats.getHttpLatencies().get_splits().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(2, stats.getHttpLatencies().get_telemetry().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(2, stats.getHttpLatencies().get_events().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getHttpLatencies().get_segments().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getHttpLatencies().get_impressions().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(1, stats.getHttpLatencies().get_impressionsCount().stream().mapToInt(Long::intValue).sum()); - Assert.assertEquals(0, stats.getHttpLatencies().get_token().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(3, stats.getHttpLatencies().getSplits().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(2, stats.getHttpLatencies().getTelemetry().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(2, stats.getHttpLatencies().getEvents().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, stats.getHttpLatencies().getSegments().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, stats.getHttpLatencies().getImpressions().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(1, stats.getHttpLatencies().getImpressionsCount().stream().mapToInt(Long::intValue).sum()); + Assert.assertEquals(0, stats.getHttpLatencies().getToken().stream().mapToInt(Long::intValue).sum()); Assert.assertEquals(2, stats.getMethodExceptions().getTreatment()); Assert.assertEquals(2, stats.getMethodExceptions().getTreatments()); Assert.assertEquals(1, stats.getMethodExceptions().getTreatmentsWithConfig()); @@ -153,34 +177,36 @@ public void testStats() throws Exception { Assert.assertEquals(0, stats.getImpressionsQueued()); Assert.assertEquals(10, stats.getEventsDropped()); Assert.assertEquals(3, stats.getEventsQueued()); - Assert.assertEquals(800, stats.getLastSynchronization().get_events()); - Assert.assertEquals(129, stats.getLastSynchronization().get_token()); - Assert.assertEquals(1580, stats.getLastSynchronization().get_segments()); - Assert.assertEquals(0, stats.getLastSynchronization().get_splits()); - Assert.assertEquals(10500, stats.getLastSynchronization().get_impressions()); - Assert.assertEquals(1500, stats.getLastSynchronization().get_impressionsCount()); - Assert.assertEquals(265, stats.getLastSynchronization().get_telemetry()); + Assert.assertEquals(800, stats.getLastSynchronization().getEvents()); + Assert.assertEquals(129, stats.getLastSynchronization().getToken()); + Assert.assertEquals(1580, stats.getLastSynchronization().getSegments()); + Assert.assertEquals(0, stats.getLastSynchronization().getSplits()); + Assert.assertEquals(10500, stats.getLastSynchronization().getImpressions()); + Assert.assertEquals(1500, stats.getLastSynchronization().getImpressionsCount()); + Assert.assertEquals(265, stats.getLastSynchronization().getTelemetry()); Assert.assertEquals(91218, stats.getSessionLengthMs()); - Assert.assertEquals(2, stats.getHttpErrors().get_telemetry().get(400l).intValue()); - Assert.assertEquals(1, stats.getHttpErrors().get_segments().get(501l).intValue()); - Assert.assertEquals(2, stats.getHttpErrors().get_impressions().get(403l).intValue()); - Assert.assertEquals(1, stats.getHttpErrors().get_impressionsCount().get(403l).intValue()); - Assert.assertEquals(1, stats.getHttpErrors().get_events().get(503l).intValue()); - Assert.assertEquals(1, stats.getHttpErrors().get_splits().get(403l).intValue()); - Assert.assertEquals(1, stats.getHttpErrors().get_token().get(403l).intValue()); + Assert.assertEquals(2, stats.getHttpErrors().getTelemetry().get(400l).intValue()); + Assert.assertEquals(1, stats.getHttpErrors().getSegments().get(501l).intValue()); + Assert.assertEquals(2, stats.getHttpErrors().getImpressions().get(403l).intValue()); + Assert.assertEquals(1, stats.getHttpErrors().getImpressionsCount().get(403l).intValue()); + Assert.assertEquals(1, stats.getHttpErrors().getEvents().get(503l).intValue()); + Assert.assertEquals(1, stats.getHttpErrors().getSplits().get(403l).intValue()); + Assert.assertEquals(1, stats.getHttpErrors().getToken().get(403l).intValue()); List streamingEvents = stats.getStreamingEvents(); - Assert.assertEquals(290, streamingEvents.get(0).get_data()); - Assert.assertEquals(1, streamingEvents.get(0).get_type()); + Assert.assertEquals(290, streamingEvents.get(0).getData()); + Assert.assertEquals(1, streamingEvents.get(0).getType()); Assert.assertEquals(91218, streamingEvents.get(0).getTimestamp()); Assert.assertEquals(1, stats.getUpdatesFromSSE().getSplits()); } - private TelemetryInMemorySubmitter getTelemetrySynchronizer(CloseableHttpClient httpClient) throws URISyntaxException { + private TelemetryInMemorySubmitter getTelemetrySynchronizer(SplitHttpClient httpClient) throws URISyntaxException { TelemetryStorageConsumer consumer = Mockito.mock(InMemoryTelemetryStorage.class); TelemetryRuntimeProducer telemetryRuntimeProducer = Mockito.mock(TelemetryRuntimeProducer.class); SplitCacheConsumer splitCacheConsumer = Mockito.mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = Mockito.mock(SegmentCacheConsumer.class); - TelemetryInMemorySubmitter telemetrySynchronizer = new TelemetryInMemorySubmitter(httpClient, URI.create(TELEMETRY_ENDPOINT), consumer, splitCacheConsumer, segmentCacheConsumer, telemetryRuntimeProducer, 0l); + TelemetryInMemorySubmitter telemetrySynchronizer = new TelemetryInMemorySubmitter(httpClient, + URI.create(TELEMETRY_ENDPOINT), consumer, splitCacheConsumer, segmentCacheConsumer, + telemetryRuntimeProducer, 0l); return telemetrySynchronizer; } @@ -266,4 +292,9 @@ private void populateConfig(TelemetryStorage telemetryStorage) { telemetryStorage.recordNonReadyUsage(); telemetryStorage.recordNonReadyUsage(); } -} \ No newline at end of file + + private SDKMetadata metadata() { + return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); + } + +} diff --git a/client/src/test/resources/semver/between-semver.csv b/client/src/test/resources/semver/between-semver.csv new file mode 100644 index 000000000..71bdf3b24 --- /dev/null +++ b/client/src/test/resources/semver/between-semver.csv @@ -0,0 +1,18 @@ +version1,version2,version3,expected +1.1.1,2.2.2,3.3.3,true +1.1.1-rc.1,1.1.1-rc.2,1.1.1-rc.3,true +1.0.0-alpha,1.0.0-alpha.1,1.0.0-alpha.beta,true +1.0.0-alpha.1,1.0.0-alpha.beta,1.0.0-beta,true +1.0.0-alpha.beta,1.0.0-beta,1.0.0-beta.2,true +1.0.0-beta,1.0.0-beta.2,1.0.0-beta.11,true +1.0.0-beta.2,1.0.0-beta.11,1.0.0-rc.1,true +1.0.0-beta.11,1.0.0-rc.1,1.0.0,true +1.1.2,1.1.3,1.1.4,true +1.2.1,1.3.1,1.4.1,true +2.0.0,3.0.0,4.0.0,true +2.2.2,2.2.3-rc1,2.2.3,true +2.2.2,2.3.2-rc100,2.3.3,true +1.0.0-rc.1+build.1,1.2.3-beta,1.2.3-rc.1+build.123,true +3.3.3,3.3.3-alpha,3.3.4,false +2.2.2-rc.1,2.2.2+metadata,2.2.2-rc.10,false +1.1.1-rc.1,1.1.1-rc.3,1.1.1-rc.2,false \ No newline at end of file diff --git a/client/src/test/resources/semver/equal-to-semver.csv b/client/src/test/resources/semver/equal-to-semver.csv new file mode 100644 index 000000000..87d8db5ae --- /dev/null +++ b/client/src/test/resources/semver/equal-to-semver.csv @@ -0,0 +1,7 @@ +version1,version2,equals +1.1.1,1.1.1,true +1.1.1,1.1.1+metadata,false +1.1.1,1.1.1-rc.1,false +88.88.88,88.88.88,true +1.2.3----RC-SNAPSHOT.12.9.1--.12,1.2.3----RC-SNAPSHOT.12.9.1--.12,true +10.2.3-DEV-SNAPSHOT,10.2.3-SNAPSHOT-123,false \ No newline at end of file diff --git a/client/src/test/resources/semver/invalid-semantic-versions.csv b/client/src/test/resources/semver/invalid-semantic-versions.csv new file mode 100644 index 000000000..7a7f9fbcf --- /dev/null +++ b/client/src/test/resources/semver/invalid-semantic-versions.csv @@ -0,0 +1,28 @@ +invalid +1 +1.2 +1.alpha.2 ++invalid +-invalid +-invalid+invalid +-invalid.01 +alpha +alpha.beta +alpha.beta.1 +alpha.1 +alpha+beta +alpha_beta +alpha. +alpha.. +beta +-alpha. +1.2 +1.2.3.DEV +1.2-SNAPSHOT +1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788 +1.2-RC-SNAPSHOT +-1.0.3-gamma+b7718 ++justmeta +1.1.1+ +1.1.1- +#99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12 \ No newline at end of file diff --git a/client/src/test/resources/semver/semver-splits.json b/client/src/test/resources/semver/semver-splits.json new file mode 100644 index 000000000..a7e58689e --- /dev/null +++ b/client/src/test/resources/semver/semver-splits.json @@ -0,0 +1,431 @@ +{ + "splits":[ + { + "trafficTypeName":"user", + "name":"semver_between", + "trafficAllocation":100, + "trafficAllocationSeed":1068038034, + "seed":-1053389887, + "status":"ACTIVE", + "killed":false, + "defaultTreatment":"off", + "changeNumber":1675259356568, + "algo":2, + "configurations":null, + "conditions":[ + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":"version" + }, + "matcherType":"BETWEEN_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":null, + "betweenStringMatcherData":{ + "start":"1.22.9", + "end":"2.1.0" + } + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":100 + }, + { + "treatment":"off", + "size":0 + } + ], + "label":"between semver" + }, + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":null + }, + "matcherType":"ALL_KEYS", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "booleanMatcherData":null, + "dependencyMatcherData":null, + "stringMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":0 + }, + { + "treatment":"off", + "size":100 + } + ], + "label":"default rule" + } + ] + }, + { + "trafficTypeName":"user", + "name":"semver_equalto", + "trafficAllocation":100, + "trafficAllocationSeed":1068038034, + "seed":-1053389887, + "status":"ACTIVE", + "killed":false, + "defaultTreatment":"off", + "changeNumber":1675259356568, + "algo":2, + "configurations":null, + "conditions":[ + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":"version" + }, + "matcherType":"EQUAL_TO_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":"1.22.9" + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":100 + }, + { + "treatment":"off", + "size":0 + } + ], + "label":"equal to semver" + }, + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":null + }, + "matcherType":"ALL_KEYS", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "booleanMatcherData":null, + "dependencyMatcherData":null, + "stringMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":0 + }, + { + "treatment":"off", + "size":100 + } + ], + "label":"default rule" + } + ] + }, + { + "trafficTypeName":"user", + "name":"semver_greater_or_equalto", + "trafficAllocation":100, + "trafficAllocationSeed":1068038034, + "seed":-1053389887, + "status":"ACTIVE", + "killed":false, + "defaultTreatment":"off", + "changeNumber":1675259356568, + "algo":2, + "configurations":null, + "conditions":[ + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":"version" + }, + "matcherType":"GREATER_THAN_OR_EQUAL_TO_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":"1.22.9" + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":100 + }, + { + "treatment":"off", + "size":0 + } + ], + "label":"greater than or equal to semver" + }, + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":null + }, + "matcherType":"ALL_KEYS", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "booleanMatcherData":null, + "dependencyMatcherData":null, + "stringMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":0 + }, + { + "treatment":"off", + "size":100 + } + ], + "label":"default rule" + } + ] + }, + { + "trafficTypeName":"user", + "name":"semver_inlist", + "trafficAllocation":100, + "trafficAllocationSeed":1068038034, + "seed":-1053389887, + "status":"ACTIVE", + "killed":false, + "defaultTreatment":"off", + "changeNumber":1675259356568, + "algo":2, + "configurations":null, + "conditions":[ + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":"version" + }, + "matcherType":"IN_LIST_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":{ + "whitelist":[ + "1.22.9", + "2.1.0" + ] + }, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":null, + "betweenStringMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":100 + }, + { + "treatment":"off", + "size":0 + } + ], + "label":"in list semver" + }, + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":null + }, + "matcherType":"ALL_KEYS", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "booleanMatcherData":null, + "dependencyMatcherData":null, + "stringMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":0 + }, + { + "treatment":"off", + "size":100 + } + ], + "label":"default rule" + } + ] + }, + { + "trafficTypeName":"user", + "name":"semver_less_or_equalto", + "trafficAllocation":100, + "trafficAllocationSeed":1068038034, + "seed":-1053389887, + "status":"ACTIVE", + "killed":false, + "defaultTreatment":"off", + "changeNumber":1675259356568, + "algo":2, + "configurations":null, + "conditions":[ + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":"version" + }, + "matcherType":"LESS_THAN_OR_EQUAL_TO_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":"1.22.9" + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":100 + }, + { + "treatment":"off", + "size":0 + } + ], + "label":"less than or equal to semver" + }, + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"user", + "attribute":null + }, + "matcherType":"ALL_KEYS", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "booleanMatcherData":null, + "dependencyMatcherData":null, + "stringMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":0 + }, + { + "treatment":"off", + "size":100 + } + ], + "label":"default rule" + } + ] + } + ], + "since":-1, + "till":1675259356568 +} \ No newline at end of file diff --git a/client/src/test/resources/semver/valid-semantic-versions.csv b/client/src/test/resources/semver/valid-semantic-versions.csv new file mode 100644 index 000000000..f491e77f2 --- /dev/null +++ b/client/src/test/resources/semver/valid-semantic-versions.csv @@ -0,0 +1,25 @@ +higher,lower +1.1.2,1.1.1 +1.0.0,1.0.0-rc.1 +1.1.0-rc.1,1.0.0-beta.11 +1.0.0-beta.11,1.0.0-beta.2 +1.0.0-beta.2,1.0.0-beta +1.0.0-beta,1.0.0-alpha.beta +1.0.0-alpha.beta,1.0.0-alpha.1 +1.0.0-alpha.1,1.0.0-alpha +2.2.2-rc.2+metadata-lalala,2.2.2-rc.1.2 +1.2.3,0.0.4 +1.1.2+meta,1.1.2-prerelease+meta +1.0.0-beta,1.0.0-alpha +1.0.0-alpha0.valid,1.0.0-alpha.0valid +1.0.0-rc.1+build.1,1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay +10.2.3-DEV-SNAPSHOT,1.2.3-SNAPSHOT-123 +1.1.1-rc2,1.0.0-0A.is.legal +1.2.3----RC-SNAPSHOT.12.9.1--.12+788,1.2.3----R-S.12.9.1--.12+meta +1.2.3----RC-SNAPSHOT.12.9.1--.12.88,1.2.3----RC-SNAPSHOT.12.9.1--.12 +9223372036854775807.9223372036854775807.9223372036854775807,9223372036854775807.9223372036854775807.9223372036854775806 +1.1.1-alpha.beta.rc.build.java.pr.support.10,1.1.1-alpha.beta.rc.build.java.pr.support +1.1.2,1.1.1 +1.2.1,1.1.1 +2.1.1,1.1.1 +1.1.1-rc.1,1.1.1-rc.0 \ No newline at end of file diff --git a/client/src/test/resources/streaming-auth-push-disabled-empty-token.json b/client/src/test/resources/streaming-auth-push-disabled-empty-token.json new file mode 100644 index 000000000..d40fd01c9 --- /dev/null +++ b/client/src/test/resources/streaming-auth-push-disabled-empty-token.json @@ -0,0 +1 @@ +{"pushEnabled":false,"token":""} \ No newline at end of file diff --git a/pluggable-storage/pom.xml b/pluggable-storage/pom.xml index b797e50fe..a6f21cd7c 100644 --- a/pluggable-storage/pom.xml +++ b/pluggable-storage/pom.xml @@ -6,7 +6,7 @@ java-client-parent io.split.client - 4.11.1 + 4.12.0 2.1.0 diff --git a/pom.xml b/pom.xml index 4c5c2871b..fc4f01fa5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.split.client java-client-parent - 4.11.1 + 4.12.0 diff --git a/redis-wrapper/pom.xml b/redis-wrapper/pom.xml index 2f9da5c8c..6f5231911 100644 --- a/redis-wrapper/pom.xml +++ b/redis-wrapper/pom.xml @@ -6,7 +6,7 @@ java-client-parent io.split.client - 4.11.1 + 4.12.0 redis-wrapper 3.1.0 diff --git a/testing/pom.xml b/testing/pom.xml index d3f815e27..0faeed8c3 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -5,7 +5,7 @@ io.split.client java-client-parent - 4.11.1 + 4.12.0 java-client-testing jar