diff --git a/.harness/pipeline.yaml b/.harness/pipeline.yaml index 83dee8309..67fab5efb 100644 --- a/.harness/pipeline.yaml +++ b/.harness/pipeline.yaml @@ -268,7 +268,7 @@ pipeline: -CommitSha "<+codebase.commitSha>" ` -SonarProjectKey "dotnet-client" ` -SonarToken "<+secrets.getValue('sonarqube-token')>" ` - -SonarUrl "https://sonar.harness.io/" + -SonarUrl "https://sonar.harness.io" - parallel: - step: type: Run diff --git a/CHANGES.txt b/CHANGES.txt index 7b6560cbe..be901588d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,11 @@ CHANGES +7.13.0 (Feb 2, 2026) +- Fixed impressions properties format in redis mode. +- Added the ability to listen to different events triggered by the SDK. Read more in our docs. + - SDK_UPDATE notify when a flag or user segment has changed + - SDK_READY notify when the SDK is ready to evaluate + 7.12.0 (Sep 30, 2025) - Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. - Added a maximum size payload when posting unique keys telemetry in batches diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 000000000..d0ea3bf0f --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,5 @@ +Harness Feature Management .NET SDK Copyright 2024-2026 Harness Inc. + +This product includes software developed at Harness Inc. (https://harness.io/). + +This product includes software originally developed by Split Software, Inc. (https://www.split.io/). Copyright 2015-2024 Split Software, Inc. diff --git a/Splitio.Redis/Services/Cache/Classes/RedisImpressionsCache.cs b/Splitio.Redis/Services/Cache/Classes/RedisImpressionsCache.cs index 28339d4a8..b5f9990fd 100644 --- a/Splitio.Redis/Services/Cache/Classes/RedisImpressionsCache.cs +++ b/Splitio.Redis/Services/Cache/Classes/RedisImpressionsCache.cs @@ -78,12 +78,13 @@ public async Task RecordImpressionsCountAsync(Dictionary impression } } - private RedisValue[] GetImpressions(IList items) + // public for tests + public RedisValue[] GetImpressions(IList items) { - var impressions = items.Select(item => JsonConvertWrapper.SerializeObject(new + var impressions = items.Select(item => JsonConvertWrapper.SerializeObjectIgnoreNullValue(new { m = new { s = SdkVersion, i = MachineIp, n = MachineName }, - i = new { k = item.keyName, b = item.bucketingKey, f = item.feature, t = item.treatment, r = item.label, c = item.changeNumber, m = item.time, pt = item.previousTime } + i = new { k = item.keyName, b = item.bucketingKey, f = item.feature, t = item.treatment, r = item.label, c = item.changeNumber, m = item.time, pt = item.previousTime, properties = item.properties } })); return impressions diff --git a/Splitio.Redis/Services/Client/Classes/RedisClient.cs b/Splitio.Redis/Services/Client/Classes/RedisClient.cs index 4cfc76eac..d36a6cf4e 100644 --- a/Splitio.Redis/Services/Client/Classes/RedisClient.cs +++ b/Splitio.Redis/Services/Client/Classes/RedisClient.cs @@ -28,15 +28,14 @@ public class RedisClient : SplitClient private IImpressionsCache _impressionsCache; private ConnectionPoolManager _connectionPoolManager; private IFeatureFlagCacheConsumer _featureFlagCacheConsumer; - private readonly new FallbackTreatmentCalculator _fallbackTreatmentCalculator; - public RedisClient(ConfigurationOptions config, string apiKey, FallbackTreatmentCalculator fallbackTreatmentCalculator) : base(apiKey, fallbackTreatmentCalculator) + public RedisClient(ConfigurationOptions config, string apiKey) : base(apiKey) { _config = new RedisConfig(); - _fallbackTreatmentCalculator = fallbackTreatmentCalculator; ReadConfig(config); + BuildFallbackCalculator(_config.FallbackTreatments); BuildRedisCache(); BuildTreatmentLog(config.ImpressionListener); @@ -77,6 +76,7 @@ private void ReadConfig(ConfigurationOptions config) _config.FlagSetsInvalid = baseConfig.FlagSetsInvalid; _config.Mode = config.Mode; _config.FromCacheAdapterConfig(config.CacheAdapterConfig); + _config.FallbackTreatments = baseConfig.FallbackTreatments; } private void BuildRedisCache() diff --git a/Splitio.Redis/Splitio.Redis.csproj b/Splitio.Redis/Splitio.Redis.csproj index 23a3ee682..ec925af21 100644 --- a/Splitio.Redis/Splitio.Redis.csproj +++ b/Splitio.Redis/Splitio.Redis.csproj @@ -7,7 +7,7 @@ false false false - 7.12.0 + 7.13.0-rc1 true SplitioRedis.snk Apache-2.0 diff --git a/src/Splitio/Constants/Constants.cs b/src/Splitio/Constants/Constants.cs index 408dfc325..0c382a1f0 100644 --- a/src/Splitio/Constants/Constants.cs +++ b/src/Splitio/Constants/Constants.cs @@ -1,7 +1,7 @@ namespace Splitio.Constants { public static class Push - { + { public static string ControlPri => "control_pri"; public static string ControlSec => "control_sec"; public static string OccupancyPrefix => "[?occupancy=metrics.publishers]"; diff --git a/src/Splitio/Domain/BaseConfig.cs b/src/Splitio/Domain/BaseConfig.cs index f4f2edf9d..a43ffa947 100644 --- a/src/Splitio/Domain/BaseConfig.cs +++ b/src/Splitio/Domain/BaseConfig.cs @@ -11,6 +11,7 @@ public class BaseConfig public ImpressionsMode ImpressionsMode { get; set; } public HashSet FlagSetsFilter { get; set; } public int FlagSetsInvalid { get; set; } + public FallbackTreatmentsConfiguration FallbackTreatments { get; set; } // Bloom Filter public int BfExpectedElements { get; set; } diff --git a/src/Splitio/Domain/EventManagerConfigData.cs b/src/Splitio/Domain/EventManagerConfigData.cs new file mode 100644 index 000000000..a68295a5a --- /dev/null +++ b/src/Splitio/Domain/EventManagerConfigData.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Splitio.Domain +{ + public class EventManagerConfigData + { + public Dictionary> RequireAll { get; protected set ; } + public Dictionary> RequireAny { get; protected set; } + public Dictionary> Prerequisites { get; protected set; } + public Dictionary> SuppressedBy { get; protected set; } + public Dictionary ExecutionLimits { get; protected set; } + public HashSet EvaluationOrder { get; protected set; } + } +} diff --git a/src/Splitio/Domain/EventMetadata.cs b/src/Splitio/Domain/EventMetadata.cs new file mode 100644 index 000000000..6b5c43025 --- /dev/null +++ b/src/Splitio/Domain/EventMetadata.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Splitio.Domain +{ + public class EventMetadata + { + private readonly List _names; + private readonly SdkEventType _type; + + public EventMetadata(SdkEventType type, List names) + { + _type = type; + _names = names; + } + + public List GetNames() { return _names; } + + public SdkEventType GetEventType() { return _type; } + } +} diff --git a/src/Splitio/Domain/EventsManagerConfig.cs b/src/Splitio/Domain/EventsManagerConfig.cs new file mode 100644 index 000000000..06d51da73 --- /dev/null +++ b/src/Splitio/Domain/EventsManagerConfig.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Splitio.Domain +{ + public class EventsManagerConfig : EventManagerConfigData + { + public EventsManagerConfig() + { + RequireAll = new Dictionary> + { + { + SdkEvent.SdkReady, new HashSet + { + SdkInternalEvent.SdkReady + } + } + }; + + Prerequisites = new Dictionary> + { + { + SdkEvent.SdkUpdate, new HashSet + { + SdkEvent.SdkReady + } + } + }; + + RequireAny = new Dictionary> + { + { SdkEvent.SdkUpdate, new HashSet + { + SdkInternalEvent.RuleBasedSegmentsUpdated, + SdkInternalEvent.FlagsUpdated, + SdkInternalEvent.FlagKilledNotification, + SdkInternalEvent.SegmentsUpdated + } + } + }; + + SuppressedBy = new Dictionary>(); + + ExecutionLimits = new Dictionary + { + { SdkEvent.SdkReady, 1 }, + { SdkEvent.SdkUpdate, -1 } + }; + + HashSet sortedEvents = new HashSet(); + foreach (SdkEvent sdkEvent in new List { SdkEvent.SdkReady, SdkEvent.SdkUpdate }) + { + sortedEvents = DFSRecursive(sdkEvent, sortedEvents); + } + + EvaluationOrder = sortedEvents; + } + + private HashSet DFSRecursive(SdkEvent sdkEvent, HashSet added) + { + if (added.Contains(sdkEvent)) return added; + + foreach (SdkEvent dependentEvent in GetDependencies(sdkEvent)) + { + added = DFSRecursive(dependentEvent, added); + } + + added.Add(sdkEvent); + + return added; + } + + private HashSet GetDependencies(SdkEvent sdkEvent) + { + HashSet dependencies = new HashSet(); + foreach (KeyValuePair> prerequisitesEvent in Prerequisites.Where(x => x.Key.Equals(sdkEvent))) + { + foreach (var prereqEvent in prerequisitesEvent.Value) + { + dependencies.Add(prereqEvent); + } + } + + foreach (KeyValuePair> suppressedEvent in SuppressedBy.Where(x => x.Value.Contains(sdkEvent))) + { + dependencies.Add(suppressedEvent.Key); + } + + return dependencies; + } + } +} diff --git a/src/Splitio/Domain/SdkEvent.cs b/src/Splitio/Domain/SdkEvent.cs new file mode 100644 index 000000000..f069df007 --- /dev/null +++ b/src/Splitio/Domain/SdkEvent.cs @@ -0,0 +1,9 @@ + +namespace Splitio.Domain +{ + public enum SdkEvent + { + SdkUpdate, + SdkReady + } +} diff --git a/src/Splitio/Domain/SdkEventNotification.cs b/src/Splitio/Domain/SdkEventNotification.cs new file mode 100644 index 000000000..737496e75 --- /dev/null +++ b/src/Splitio/Domain/SdkEventNotification.cs @@ -0,0 +1,16 @@ +using Splitio.Domain; + +namespace Splitio.Services.EventSource.Workers +{ + public class SdkEventNotification + { + public SdkInternalEvent SdkInternalEvent { get; set; } + public EventMetadata EventMetadata { get; set; } + + public SdkEventNotification(SdkInternalEvent sdkInternalEvent, EventMetadata eventMetadata) + { + SdkInternalEvent = sdkInternalEvent; + EventMetadata = eventMetadata; + } + } +} diff --git a/src/Splitio/Domain/SdkEventType.cs b/src/Splitio/Domain/SdkEventType.cs new file mode 100644 index 000000000..c8ba6f92d --- /dev/null +++ b/src/Splitio/Domain/SdkEventType.cs @@ -0,0 +1,9 @@ + +namespace Splitio.Domain +{ + public enum SdkEventType + { + SegmentsUpdate, + FlagsUpdate + } +} diff --git a/src/Splitio/Domain/SdkInternalEvent.cs b/src/Splitio/Domain/SdkInternalEvent.cs new file mode 100644 index 000000000..64e3b0eb2 --- /dev/null +++ b/src/Splitio/Domain/SdkInternalEvent.cs @@ -0,0 +1,13 @@ + +namespace Splitio.Domain +{ + public enum SdkInternalEvent + { + FlagsUpdated, + FlagKilledNotification, + RuleBasedSegmentsUpdated, + SegmentsUpdated, + LargeSegmentsUpdated, + SdkReady + } +} diff --git a/src/Splitio/Services/Cache/Classes/InMemoryReadinessGatesCache.cs b/src/Splitio/Services/Cache/Classes/InMemoryReadinessGatesCache.cs index d1d42e51c..f97f08d5a 100644 --- a/src/Splitio/Services/Cache/Classes/InMemoryReadinessGatesCache.cs +++ b/src/Splitio/Services/Cache/Classes/InMemoryReadinessGatesCache.cs @@ -1,5 +1,8 @@ -using Splitio.Services.Cache.Interfaces; +using Splitio.Domain; +using Splitio.Services.Cache.Interfaces; +using Splitio.Services.Tasks; using System.Threading; +using System.Threading.Tasks; namespace Splitio.Services.Client.Classes { @@ -7,6 +10,12 @@ public class InMemoryReadinessGatesCache : IStatusManager { private readonly CountdownEvent _sdkReady = new CountdownEvent(1); private readonly CountdownEvent _sdkDestroyed = new CountdownEvent(1); + private readonly IInternalEventsTask _internalEventsTask; + + public InMemoryReadinessGatesCache(IInternalEventsTask internalEventsTask) + { + _internalEventsTask = internalEventsTask; + } public bool IsReady() { @@ -18,9 +27,10 @@ public bool WaitUntilReady(int milliseconds) return _sdkReady.Wait(milliseconds); } - public void SetReady() + public async Task SetReadyAsync() { _sdkReady.Signal(); + await _internalEventsTask.AddToQueue(SdkInternalEvent.SdkReady, null); } public void SetDestroy() diff --git a/src/Splitio/Services/Cache/Classes/InMemoryRuleBasedSegmentCache.cs b/src/Splitio/Services/Cache/Classes/InMemoryRuleBasedSegmentCache.cs index 554e20358..3aeaef60c 100644 --- a/src/Splitio/Services/Cache/Classes/InMemoryRuleBasedSegmentCache.cs +++ b/src/Splitio/Services/Cache/Classes/InMemoryRuleBasedSegmentCache.cs @@ -1,5 +1,8 @@ using Splitio.Domain; using Splitio.Services.Cache.Interfaces; +using Splitio.Services.Logger; +using Splitio.Services.Shared.Classes; +using Splitio.Services.Tasks; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; @@ -10,12 +13,16 @@ public class InMemoryRuleBasedSegmentCache : IRuleBasedSegmentCache { private readonly ConcurrentDictionary _cache; private long _changeNumber; + private readonly IInternalEventsTask _internalEventsTask; + private readonly ISplitLogger _log = WrapperAdapter.Instance().GetLogger(typeof(InMemoryRuleBasedSegmentCache)); public InMemoryRuleBasedSegmentCache(ConcurrentDictionary cache, + IInternalEventsTask internalEventsTask, long changeNumber = -1) { _cache = cache; _changeNumber = changeNumber; + _internalEventsTask = internalEventsTask; } #region Sync Methods @@ -56,6 +63,15 @@ public void Update(List toAdd, List toRemove, long til } SetChangeNumber(till); + if (toAdd.Count > 0 || toRemove.Count > 0) + { + Task task = new Task(() => + { + _internalEventsTask.AddToQueue(SdkInternalEvent.RuleBasedSegmentsUpdated, + new EventMetadata(SdkEventType.SegmentsUpdate, new List())).ContinueWith(OnAddToQueueFailed, TaskContinuationOptions.OnlyOnFaulted); + }); + task.Start(); + } } public void SetChangeNumber(long changeNumber) @@ -75,5 +91,10 @@ public Task GetAsync(string name) return Task.FromResult(Get(name)); } #endregion + + public void OnAddToQueueFailed(Task task) + { + _log.Error($"Failed to add internal event to queue: {task.Exception.Message}"); + } } } diff --git a/src/Splitio/Services/Cache/Classes/InMemorySegmentCache.cs b/src/Splitio/Services/Cache/Classes/InMemorySegmentCache.cs index f94a3d51f..243d9c6e1 100644 --- a/src/Splitio/Services/Cache/Classes/InMemorySegmentCache.cs +++ b/src/Splitio/Services/Cache/Classes/InMemorySegmentCache.cs @@ -2,6 +2,7 @@ using Splitio.Services.Cache.Interfaces; using Splitio.Services.Logger; using Splitio.Services.Shared.Classes; +using Splitio.Services.Tasks; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; @@ -13,10 +14,12 @@ public class InMemorySegmentCache : ISegmentCache private readonly ISplitLogger _log = WrapperAdapter.Instance().GetLogger(typeof(InMemorySegmentCache)); private readonly ConcurrentDictionary _segments; + private readonly IInternalEventsTask _internalEventsTask; - public InMemorySegmentCache(ConcurrentDictionary segments) + public InMemorySegmentCache(ConcurrentDictionary segments, IInternalEventsTask internalEventsTask) { _segments = segments; + _internalEventsTask = internalEventsTask; } #region Methods Sync @@ -31,6 +34,12 @@ public void AddToSegment(string segmentName, List segmentKeys) } segment.AddKeys(segmentKeys); + Task task = new Task(() => + { + _internalEventsTask.AddToQueue(SdkInternalEvent.SegmentsUpdated, + new EventMetadata(SdkEventType.SegmentsUpdate, new List())).ContinueWith(OnAddToQueueFailed, TaskContinuationOptions.OnlyOnFaulted); + }); + task.Start(); } public void RemoveFromSegment(string segmentName, List segmentKeys) @@ -38,6 +47,12 @@ public void RemoveFromSegment(string segmentName, List segmentKeys) if (_segments.TryGetValue(segmentName, out Segment segment)) { segment.RemoveKeys(segmentKeys); + Task task = new Task(() => + { + _internalEventsTask.AddToQueue(SdkInternalEvent.SegmentsUpdated, + new EventMetadata(SdkEventType.SegmentsUpdate, new List())).ContinueWith(OnAddToQueueFailed, TaskContinuationOptions.OnlyOnFaulted); + }); + task.Start(); } } @@ -108,5 +123,10 @@ public Task IsInSegmentAsync(string segmentName, string key) return Task.FromResult(IsInSegment(segmentName, key)); } #endregion + + public void OnAddToQueueFailed(Task task) + { + _log.Error($"Failed to add internal event to queue: {task.Exception.Message}"); + } } } diff --git a/src/Splitio/Services/Cache/Classes/InMemorySplitCache.cs b/src/Splitio/Services/Cache/Classes/InMemorySplitCache.cs index 8bbf35293..6063d43e2 100644 --- a/src/Splitio/Services/Cache/Classes/InMemorySplitCache.cs +++ b/src/Splitio/Services/Cache/Classes/InMemorySplitCache.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Splitio.Services.Tasks; namespace Splitio.Services.Cache.Classes { @@ -18,11 +19,12 @@ public class InMemorySplitCache : IFeatureFlagCache private readonly ConcurrentDictionary _featureFlags; private readonly ConcurrentDictionary _trafficTypes; private readonly ConcurrentDictionary> _flagSets; + private readonly IInternalEventsTask _internalEventsTask; private long _changeNumber; public InMemorySplitCache(ConcurrentDictionary featureFlags, - IFlagSetsFilter flagSetsFilter, + IFlagSetsFilter flagSetsFilter, IInternalEventsTask internalEventsTask, long changeNumber = -1) { _featureFlags = featureFlags; @@ -30,6 +32,7 @@ public InMemorySplitCache(ConcurrentDictionary featureFlags _changeNumber = changeNumber; _trafficTypes = new ConcurrentDictionary(); _flagSets = new ConcurrentDictionary>(); + _internalEventsTask = internalEventsTask; if (!_featureFlags.IsEmpty) { @@ -47,6 +50,8 @@ public InMemorySplitCache(ConcurrentDictionary featureFlags #region Sync Methods public void Update(List toAdd, List toRemove, long till) { + List eventsFlags = new List(); + foreach (var featureFlag in toAdd) { if (_featureFlags.TryGetValue(featureFlag.name, out ParsedSplit existing)) @@ -56,6 +61,7 @@ public void Update(List toAdd, List toRemove, long till) } _featureFlags.AddOrUpdate(featureFlag.name, featureFlag, (key, oldValue) => featureFlag); + eventsFlags.Add(featureFlag.name); IncreaseTrafficTypeCount(featureFlag.trafficTypeName); AddToFlagSets(featureFlag); @@ -67,10 +73,20 @@ public void Update(List toAdd, List toRemove, long till) { DecreaseTrafficTypeCount(removedSplit); RemoveFromFlagSets(removedSplit.name, removedSplit.Sets); + eventsFlags.Add(featureFlagName); } } SetChangeNumber(till); + if (eventsFlags.Any()) + { + Task task = new Task(() => + { + _internalEventsTask.AddToQueue(SdkInternalEvent.FlagsUpdated, + new EventMetadata(SdkEventType.FlagsUpdate, eventsFlags)).ContinueWith(OnAddToQueueFailed, TaskContinuationOptions.OnlyOnFaulted); + }); + task.Start(); + } } public void SetChangeNumber(long changeNumber) @@ -142,6 +158,12 @@ public void Kill(long changeNumber, string splitName, string defaultTreatment) featureFlag.changeNumber = changeNumber; _featureFlags.AddOrUpdate(featureFlag.name, featureFlag, (key, oldValue) => featureFlag); + Task task = new Task(() => + { + _internalEventsTask.AddToQueue(SdkInternalEvent.FlagKilledNotification, + new EventMetadata(SdkEventType.FlagsUpdate, new List { { featureFlag.name } })).ContinueWith(OnAddToQueueFailed, TaskContinuationOptions.OnlyOnFaulted); + }); + task.Start(); } public List GetSplitNames() @@ -263,5 +285,10 @@ private void RemoveNames(string key, string name) if (names.Count == 0) _flagSets.TryRemove(key, out HashSet _); } #endregion + + public void OnAddToQueueFailed(Task task) + { + _log.Error($"Failed to add internal event to queue: {task.Exception.Message}"); + } } } diff --git a/src/Splitio/Services/Cache/Interfaces/IStatusManager.cs b/src/Splitio/Services/Cache/Interfaces/IStatusManager.cs index ebfe0ffe4..c27475dbe 100644 --- a/src/Splitio/Services/Cache/Interfaces/IStatusManager.cs +++ b/src/Splitio/Services/Cache/Interfaces/IStatusManager.cs @@ -1,10 +1,12 @@ -namespace Splitio.Services.Cache.Interfaces +using System.Threading.Tasks; + +namespace Splitio.Services.Cache.Interfaces { public interface IStatusManager { bool IsReady(); bool WaitUntilReady(int milliseconds); - void SetReady(); + Task SetReadyAsync(); void SetDestroy(); bool IsDestroyed(); } diff --git a/src/Splitio/Services/Client/Classes/JSONFileClient.cs b/src/Splitio/Services/Client/Classes/JSONFileClient.cs index f6bdc5932..34bd71946 100644 --- a/src/Splitio/Services/Client/Classes/JSONFileClient.cs +++ b/src/Splitio/Services/Client/Classes/JSONFileClient.cs @@ -23,7 +23,7 @@ public class JSONFileClient : SplitClient public JSONFileClient(string splitsFilePath, string segmentsFilePath, - FallbackTreatmentCalculator fallbackTreatmentCalculator, + ConfigurationOptions config, ISegmentCache segmentCacheInstance = null, IFeatureFlagCache featureFlagCacheInstance = null, IImpressionsLog impressionsLog = null, @@ -32,10 +32,10 @@ public JSONFileClient(string splitsFilePath, ITrafficTypeValidator trafficTypeValidator = null, IImpressionsManager impressionsManager = null, IRuleBasedSegmentCache ruleBasedSegmentCache = null - ) : base("localhost", fallbackTreatmentCalculator) + ) : base("localhost") { - _segmentCache = segmentCacheInstance ?? new InMemorySegmentCache(new ConcurrentDictionary()); - var rbsCache = ruleBasedSegmentCache ?? new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary()); + _segmentCache = segmentCacheInstance ?? new InMemorySegmentCache(new ConcurrentDictionary(), _internalEventsTask); + var rbsCache = ruleBasedSegmentCache ?? new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary(), _internalEventsTask); var segmentFetcher = new JSONFileSegmentFetcher(segmentsFilePath, _segmentCache); var splitChangeFetcher = new JSONFileSplitChangeFetcher(splitsFilePath); @@ -52,15 +52,15 @@ public JSONFileClient(string splitsFilePath, parsedSplits.TryAdd(split.name, _splitParser.Parse(split, rbsCache)); } + BuildFallbackCalculator(config.FallbackTreatments); BuildFlagSetsFilter(new HashSet()); - - _featureFlagCache = featureFlagCacheInstance ?? new InMemorySplitCache(new ConcurrentDictionary(parsedSplits), _flagSetsFilter); + _featureFlagCache = featureFlagCacheInstance ?? new InMemorySplitCache(new ConcurrentDictionary(parsedSplits), _flagSetsFilter, _internalEventsTask); _impressionsLog = impressionsLog; _eventsLog = eventsLog; _trafficTypeValidator = trafficTypeValidator; _blockUntilReadyService = new NoopBlockUntilReadyService(); _manager = new SplitManager(_featureFlagCache, _blockUntilReadyService); - _evaluator = new Evaluator.Evaluator(_featureFlagCache, new Splitter(), null, fallbackTreatmentCalculator); + _evaluator = new Evaluator.Evaluator(_featureFlagCache, new Splitter(), null, _fallbackTreatmentCalculator); _uniqueKeysTracker = new NoopUniqueKeysTracker(); _impressionsCounter = new NoopImpressionsCounter(); _impressionsObserver = new NoopImpressionsObserver(); diff --git a/src/Splitio/Services/Client/Classes/LocalhostClient.cs b/src/Splitio/Services/Client/Classes/LocalhostClient.cs index c1a9fbd38..92cb305f6 100644 --- a/src/Splitio/Services/Client/Classes/LocalhostClient.cs +++ b/src/Splitio/Services/Client/Classes/LocalhostClient.cs @@ -27,7 +27,7 @@ public class LocalhostClient : SplitClient private readonly object _lock = new object(); - public LocalhostClient(ConfigurationOptions configurationOptions, FallbackTreatmentCalculator fallbackTreatmentCalculator) : base("localhost", fallbackTreatmentCalculator) + public LocalhostClient(ConfigurationOptions configurationOptions) : base("localhost") { var configs = (LocalhostClientConfigurations)_configService.ReadConfig(configurationOptions, ConfigTypes.Localhost, _statusManager); @@ -44,10 +44,11 @@ public LocalhostClient(ConfigurationOptions configurationOptions, FallbackTreatm _localhostFileService = new LocalhostFileService(); } + BuildFallbackCalculator(configs.FallbackTreatments); BuildFlagSetsFilter(new HashSet()); var splits = _localhostFileService.ParseSplitFile(_fullPath); - _featureFlagCache = new InMemorySplitCache(splits, _flagSetsFilter); + _featureFlagCache = new InMemorySplitCache(splits, _flagSetsFilter, _internalEventsTask); if (configs.FileSync != null) @@ -62,7 +63,7 @@ public LocalhostClient(ConfigurationOptions configurationOptions, FallbackTreatm _blockUntilReadyService = new NoopBlockUntilReadyService(); _manager = new SplitManager(_featureFlagCache, _blockUntilReadyService); _trafficTypeValidator = new TrafficTypeValidator(_featureFlagCache, _blockUntilReadyService); - _evaluator = new Evaluator.Evaluator(_featureFlagCache, new Splitter(), null, fallbackTreatmentCalculator); + _evaluator = new Evaluator.Evaluator(_featureFlagCache, new Splitter(), null, _fallbackTreatmentCalculator); _uniqueKeysTracker = new NoopUniqueKeysTracker(); _impressionsCounter = new NoopImpressionsCounter(); _impressionsObserver = new NoopImpressionsObserver(); diff --git a/src/Splitio/Services/Client/Classes/SelfRefreshingClient.cs b/src/Splitio/Services/Client/Classes/SelfRefreshingClient.cs index e38879051..b90e6e748 100644 --- a/src/Splitio/Services/Client/Classes/SelfRefreshingClient.cs +++ b/src/Splitio/Services/Client/Classes/SelfRefreshingClient.cs @@ -51,14 +51,12 @@ public class SelfRefreshingClient : SplitClient private IUpdater _featureFlagUpdater; private IRuleBasedSegmentCache _ruleBasedSegmentCache; private IUpdater _ruleBasedSegmentUpdater; - private readonly new FallbackTreatmentCalculator _fallbackTreatmentCalculator; - public SelfRefreshingClient(string apiKey, ConfigurationOptions config, - FallbackTreatmentCalculator fallbackTreatmentCalculator) : base(apiKey, fallbackTreatmentCalculator) + public SelfRefreshingClient(string apiKey, ConfigurationOptions config) : base(apiKey) { _config = (SelfRefreshingConfig)_configService.ReadConfig(config, ConfigTypes.InMemory); - _fallbackTreatmentCalculator = fallbackTreatmentCalculator; + BuildFallbackCalculator(_config.FallbackTreatments); BuildFlagSetsFilter(_config.FlagSetsFilter); BuildSplitCache(); BuildSegmentCache(); @@ -90,17 +88,17 @@ public SelfRefreshingClient(string apiKey, ConfigurationOptions config, #region Private Methods private void BuildSplitCache() { - _featureFlagCache = new InMemorySplitCache(new ConcurrentDictionary(_config.ConcurrencyLevel, InitialCapacity), _flagSetsFilter); + _featureFlagCache = new InMemorySplitCache(new ConcurrentDictionary(_config.ConcurrencyLevel, InitialCapacity), _flagSetsFilter, _internalEventsTask); } private void BuildSegmentCache() { - _segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(_config.ConcurrencyLevel, InitialCapacity)); + _segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(_config.ConcurrencyLevel, InitialCapacity), _internalEventsTask); } private void BuildRuleBasedSegmentCache() { - _ruleBasedSegmentCache = new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary(_config.ConcurrencyLevel, InitialCapacity)); + _ruleBasedSegmentCache = new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary(_config.ConcurrencyLevel, InitialCapacity), _internalEventsTask); } private void BuildTelemetryStorage() diff --git a/src/Splitio/Services/Client/Classes/SplitClient.cs b/src/Splitio/Services/Client/Classes/SplitClient.cs index 54297fdf8..ef5867aba 100644 --- a/src/Splitio/Services/Client/Classes/SplitClient.cs +++ b/src/Splitio/Services/Client/Classes/SplitClient.cs @@ -39,7 +39,6 @@ public abstract class SplitClient : ISplitClient protected readonly IConfigService _configService; protected readonly IFlagSetsValidator _flagSetsValidator; protected readonly string ApiKey; - protected readonly FallbackTreatmentCalculator _fallbackTreatmentCalculator; protected ISplitManager _manager; protected IEventsLog _eventsLog; @@ -63,12 +62,37 @@ public abstract class SplitClient : ISplitClient protected IImpressionsObserver _impressionsObserver; protected IClientExtensionService _clientExtensionService; protected IFlagSetsFilter _flagSetsFilter; + protected IFallbackTreatmentCalculator _fallbackTreatmentCalculator; + protected IEventsManager _eventsManager; + protected IInternalEventsTask _internalEventsTask; + private EventHandler SdkReadyEvent; - protected SplitClient(string apikey, FallbackTreatmentCalculator fallbackTreatmentCalculator) + public event EventHandler SdkReady { - ApiKey = apikey; + add + { + SdkReadyEvent = (EventHandler)Delegate.Combine(SdkReadyEvent, value); + if (_eventsManager.EventAlreadyTriggered(SdkEvent.SdkReady)) + { + SdkReadyEvent.Invoke(this, null); + } + } - _fallbackTreatmentCalculator = fallbackTreatmentCalculator; + remove + { + SdkReadyEvent = (EventHandler)Delegate.Remove(SdkReadyEvent, value); + } + } + public event EventHandler SdkUpdate; + + protected SplitClient(string apikey) + { + ApiKey = apikey; + _eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + _internalEventsTask = new InternalEventsTask(_eventsManager, new SplitQueue()); + _internalEventsTask.Start(); + RegisterEvents(); + _wrapperAdapter = WrapperAdapter.Instance(); _keyValidator = new KeyValidator(); _splitNameValidator = new SplitNameValidator(); @@ -77,7 +101,7 @@ protected SplitClient(string apikey, FallbackTreatmentCalculator fallbackTreatme _factoryInstantiationsService = FactoryInstantiationsService.Instance(); _flagSetsValidator = new FlagSetsValidator(); _configService = new ConfigService(_wrapperAdapter, _flagSetsValidator, new SdkMetadataValidator()); - _statusManager = new InMemoryReadinessGatesCache(); + _statusManager = new InMemoryReadinessGatesCache(_internalEventsTask); _tasksManager = new TasksManager(_statusManager); } @@ -295,10 +319,11 @@ public virtual async Task DestroyAsync() if (_statusManager.IsDestroyed()) return; _log.Info(Messages.InitDestroy); - + _internalEventsTask.Stop(); _factoryInstantiationsService.Decrease(ApiKey); _statusManager.SetDestroy(); await _syncManager.ShutdownAsync(); + UnregisterEvents(); _log.Info(Messages.Destroyed); } @@ -309,9 +334,11 @@ public virtual void Destroy() _log.Info(Messages.InitDestroy); + _internalEventsTask.Stop(); _factoryInstantiationsService.Decrease(ApiKey); _statusManager.SetDestroy(); _syncManager.Shutdown(); + UnregisterEvents(); _log.Info(Messages.Destroyed); } @@ -421,6 +448,11 @@ protected void BuildFlagSetsFilter(HashSet sets) { _flagSetsFilter = new FlagSetsFilter(sets); } + + protected void BuildFallbackCalculator(FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) + { + _fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); + } #endregion #region Private Async Methods @@ -482,6 +514,16 @@ private async Task TrackImpressionsAsync(List evaluatorResults, #endregion #region Private Methods + private void RegisterEvents() + { + _eventsManager.Register(SdkEvent.SdkReady, TriggerSdkReady); + _eventsManager.Register(SdkEvent.SdkUpdate, TriggerSdkUpdate); + } + private void UnregisterEvents() + { + _eventsManager.Unregister(SdkEvent.SdkReady); + _eventsManager.Unregister(SdkEvent.SdkUpdate); + } private List GetTreatmentsSync(Enums.API method, Key key, List features, Dictionary attributes = null, EvaluationOptions evaluationOptions = null) { try @@ -569,6 +611,16 @@ private static SplitResult TreatmentWithConfig(List results) return new SplitResult(result.Treatment, result.Config); } + + private void TriggerSdkReady(EventMetadata metaData) + { + SdkReadyEvent?.Invoke(this, metaData); + } + + private void TriggerSdkUpdate(EventMetadata metaData) + { + SdkUpdate?.Invoke(this, metaData); + } #endregion } } \ No newline at end of file diff --git a/src/Splitio/Services/Client/Classes/SplitFactory.cs b/src/Splitio/Services/Client/Classes/SplitFactory.cs index 92f8b46ec..1cb95e988 100644 --- a/src/Splitio/Services/Client/Classes/SplitFactory.cs +++ b/src/Splitio/Services/Client/Classes/SplitFactory.cs @@ -1,6 +1,5 @@ using Splitio.Domain; using Splitio.Services.Client.Interfaces; -using Splitio.Services.Impressions.Classes; using Splitio.Services.InputValidation.Classes; using Splitio.Services.InputValidation.Interfaces; using Splitio.Services.Shared.Classes; @@ -58,7 +57,6 @@ public ISplitManager Manager() private void BuildSplitClient() { - FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(_options.FallbackTreatments); switch (_options.Mode) { case Mode.Standalone: @@ -66,11 +64,11 @@ private void BuildSplitClient() if (_apiKey == "localhost") { - _client = new LocalhostClient(_options, fallbackTreatmentCalculator); + _client = new LocalhostClient(_options); } else { - _client = new SelfRefreshingClient(_apiKey, _options, fallbackTreatmentCalculator); + _client = new SelfRefreshingClient(_apiKey, _options); } break; case Mode.Consumer: @@ -81,7 +79,7 @@ private void BuildSplitClient() var redisAssembly = Assembly.Load(new AssemblyName("Splitio.Redis")); var redisType = redisAssembly.GetType("Splitio.Redis.Services.Client.Classes.RedisClient"); - _client = (ISplitClient)Activator.CreateInstance(redisType, new object[] { _options, _apiKey, fallbackTreatmentCalculator }); + _client = (ISplitClient)Activator.CreateInstance(redisType, new object[] { _options, _apiKey }); } catch (ArgumentException ex) { diff --git a/src/Splitio/Services/Client/Interfaces/ISplitClient.cs b/src/Splitio/Services/Client/Interfaces/ISplitClient.cs index 60e0b751b..78502783c 100644 --- a/src/Splitio/Services/Client/Interfaces/ISplitClient.cs +++ b/src/Splitio/Services/Client/Interfaces/ISplitClient.cs @@ -1,10 +1,14 @@ using Splitio.Domain; +using System; using System.Collections.Generic; namespace Splitio.Services.Client.Interfaces { public interface ISplitClient : ISplitClientAsync { + event EventHandler SdkReady; + event EventHandler SdkUpdate; + /// /// Returns the treatment to show this key for this feature flag. /// The set of treatments for a feature flag can be configured on the Split user interface. diff --git a/src/Splitio/Services/Common/EventDelivery.cs b/src/Splitio/Services/Common/EventDelivery.cs new file mode 100644 index 000000000..c2dfbef41 --- /dev/null +++ b/src/Splitio/Services/Common/EventDelivery.cs @@ -0,0 +1,46 @@ +using Splitio.Services.Logger; +using Splitio.Services.Shared.Classes; +using System; +using System.Threading; + +namespace Splitio.Services.Common +{ + public class EventDelivery : IEventDelivery + { + private readonly ISplitLogger _logger = WrapperAdapter.Instance().GetLogger("EventDelivery"); + + public void Deliver(E sdkEvent, M eventMetadata, Action handler) + { + try + { + object[] parameters = new object[] { handler, eventMetadata }; + ThreadPool.QueueUserWorkItem(RunCallbackAction, parameters); + } + catch (Exception ex) + { + if (ex is OperationCanceledException) return; + + _logger.Debug($"EventDelivery worker Execute exception", ex); + } + } + + private void RunCallbackAction(object state) + { + try + { + if (state is object[] parameters) + { + Action callbackAction = (Action)parameters[0]; + M eventMetadata = (M)parameters[1]; + callbackAction(eventMetadata); + } + } + catch (Exception ex) + { + if (ex is OperationCanceledException) return; + + _logger.Debug($"Exception in callback", ex); + } + } + } +} diff --git a/src/Splitio/Services/Common/EventsManager.cs b/src/Splitio/Services/Common/EventsManager.cs new file mode 100644 index 000000000..9ce90a245 --- /dev/null +++ b/src/Splitio/Services/Common/EventsManager.cs @@ -0,0 +1,235 @@ +using Splitio.Domain; +using Splitio.Services.Logger; +using Splitio.Services.Shared.Classes; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Splitio.Services.Common +{ + public class EventsManager : IEventsManager + { + public struct ValidSdkEvent + { + public E SdkEvent { get; set; } + public bool Valid { get; set; } + } + + private struct PublicEventProperties + { + public bool Triggered; + public Action EventHandler; + } + private readonly ConcurrentDictionary _activeSubscriptions; + private readonly ConcurrentDictionary _internalEventsStatus; + private readonly ISplitLogger _logger = WrapperAdapter.Instance().GetLogger("EventsManager"); + private readonly IEventDelivery _eventDelivery; + private readonly object _lock = new object(); + public EventManagerConfigData _managerConfig { get; private set; } + + public EventsManager(EventManagerConfigData eventsManagerConfig, IEventDelivery eventDelivery) + { + _activeSubscriptions = new ConcurrentDictionary(); + _internalEventsStatus = new ConcurrentDictionary(); + _eventDelivery = eventDelivery; + _managerConfig = eventsManagerConfig; + } + + #region Public Methods + public void Register(E sdkEvent, Action handler) + { + if (_activeSubscriptions.TryGetValue(sdkEvent, out var _)) + { + return; + } + + _activeSubscriptions.TryAdd(sdkEvent, new PublicEventProperties + { + Triggered = false, + EventHandler = handler + }); + _logger.Debug($"EventsManager: Event {sdkEvent} is registered"); + } + + public void Unregister(E sdkEvent) + { + if (_activeSubscriptions.TryRemove(sdkEvent, out _)) + { + _logger.Debug($"EventsManager: Event {sdkEvent} is Unregistered"); + } + } + + public void NotifyInternalEvent(I sdkInternalEvent, M eventMetadata) + { + lock (_lock) + { + foreach (E sortedEvent in _managerConfig.EvaluationOrder.Where(x => GetSdkEventIfApplicable(sdkInternalEvent).Contains(x))) + { + _logger.Debug($"EventsManager: Firing Sdk event {sortedEvent}"); + _eventDelivery.Deliver(sortedEvent, eventMetadata, GetEventHandler(sortedEvent)); + SetSdkEventTriggered(sortedEvent); + } + } + } + + public bool EventAlreadyTriggered(E sdkEvent) + { + if (_activeSubscriptions.TryGetValue(sdkEvent, out PublicEventProperties eventProperties)) + { + return eventProperties.Triggered; + } + return false; + } + #endregion + + #region Private Methods + private bool GetSdkInternalEventStatus(I sdkInternalEvent) + { + _internalEventsStatus.TryGetValue(sdkInternalEvent, out var status); + return status; + } + + private void UpdateSdkInternalEventStatus(I sdkInternalEvent, bool status) + { + _internalEventsStatus.AddOrUpdate(sdkInternalEvent, status, + (_, oldValue) => status); + } + + private void SetSdkEventTriggered(E sdkEvent) + { + if (!_activeSubscriptions.TryGetValue(sdkEvent, out var eventData)) + { + return; + } + + if (eventData.Triggered) + { + return; + } + + PublicEventProperties newEventData = eventData; + newEventData.Triggered = true; + _activeSubscriptions.TryUpdate(sdkEvent, newEventData, eventData); + } + + private Action GetEventHandler(E sdkEvent) + { + if (!_activeSubscriptions.TryGetValue(sdkEvent, out var eventData)) + { + return null; + } + + return eventData.EventHandler; + } + + public List GetSdkEventIfApplicable(I sdkInternalEvent) + { + ValidSdkEvent finalSdkEvent = new ValidSdkEvent + { + Valid = false + }; + UpdateSdkInternalEventStatus(sdkInternalEvent, true); + List eventsToFire = new List(); + + ValidSdkEvent requireAnySdkEvent = CheckRequireAny(sdkInternalEvent); + if (requireAnySdkEvent.Valid) + { + if ((!EventAlreadyTriggered(requireAnySdkEvent.SdkEvent) + && ExecutionLimit(requireAnySdkEvent.SdkEvent) == 1) || ExecutionLimit(requireAnySdkEvent.SdkEvent) == -1) + { + finalSdkEvent.SdkEvent = requireAnySdkEvent.SdkEvent; + } + + finalSdkEvent.Valid = CheckPrerequisites(finalSdkEvent.SdkEvent) + && CheckSuppressedBy(finalSdkEvent.SdkEvent); + + if (finalSdkEvent.Valid) + { + eventsToFire.Add(finalSdkEvent.SdkEvent); + } + } + + foreach (E sdkEvent in CheckRequireAll()) + { + eventsToFire.Add(sdkEvent); + } + + return eventsToFire; + } + + private List CheckRequireAll() + { + List events = new List(); + foreach (KeyValuePair> kvp in _managerConfig.RequireAll) + { + bool finalStatus = true; + foreach (var val in kvp.Value) + { + finalStatus &= GetSdkInternalEventStatus(val); + } + if (finalStatus + && CheckPrerequisites(kvp.Key) + && ((ExecutionLimit(kvp.Key) == 1 && !EventAlreadyTriggered(kvp.Key)) + || (ExecutionLimit(kvp.Key) == -1)) + && kvp.Value.Count > 0) + { + events.Add(kvp.Key); + } + } + + return events; + } + + private bool CheckPrerequisites(E sdkEvent) + { + if (_managerConfig.Prerequisites.Any(kvp => kvp.Key.Equals(sdkEvent) && + kvp.Value.Any(x => !EventAlreadyTriggered(x)))) + { + return false; + } + + return true; + } + + private bool CheckSuppressedBy(E sdkEvent) + { + if (_managerConfig.SuppressedBy.Any(kvp => kvp.Key.Equals(sdkEvent) && + kvp.Value.Any(x => EventAlreadyTriggered(x)))) + { + return false; + } + + return true; + } + + private int ExecutionLimit(E sdkEvent) + { + if (!_managerConfig.ExecutionLimits.TryGetValue(sdkEvent, out int limit)) + { + return -1; + } + + return limit; + } + + private ValidSdkEvent CheckRequireAny(I sdkInternalEvent) + { + ValidSdkEvent validSdkEvent = new ValidSdkEvent + { + Valid = false + }; + + var sdkEvent = _managerConfig.RequireAny.Where(kvp => kvp.Value.Contains(sdkInternalEvent)); + if (sdkEvent.Any()) + { + validSdkEvent.Valid = true; + validSdkEvent.SdkEvent = sdkEvent.First().Key; + return validSdkEvent; + } + + return validSdkEvent; + } + #endregion + } +} diff --git a/src/Splitio/Services/Common/IEventDelivery.cs b/src/Splitio/Services/Common/IEventDelivery.cs new file mode 100644 index 000000000..83bb4540d --- /dev/null +++ b/src/Splitio/Services/Common/IEventDelivery.cs @@ -0,0 +1,9 @@ +using System; + +namespace Splitio.Services.Common +{ + public interface IEventDelivery + { + void Deliver(E sdkEvent, M eventMetadata, Action handler); + } +} diff --git a/src/Splitio/Services/Common/IEventsManager.cs b/src/Splitio/Services/Common/IEventsManager.cs new file mode 100644 index 000000000..ac5f37396 --- /dev/null +++ b/src/Splitio/Services/Common/IEventsManager.cs @@ -0,0 +1,12 @@ +using System; + +namespace Splitio.Services.Common +{ + public interface IEventsManager + { + void NotifyInternalEvent(I sdkInternalEvent, M eventMetadata); + void Register(E sdkEvent, Action handler); + void Unregister(E sdkEvent); + bool EventAlreadyTriggered(E sdkEvent); + } +} diff --git a/src/Splitio/Services/Common/SyncManager.cs b/src/Splitio/Services/Common/SyncManager.cs index eb70f7518..4fee070e6 100644 --- a/src/Splitio/Services/Common/SyncManager.cs +++ b/src/Splitio/Services/Common/SyncManager.cs @@ -11,9 +11,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Threading; using System.Threading.Tasks; -using YamlDotNet.Serialization.NodeTypeResolvers; namespace Splitio.Services.Common { @@ -32,7 +30,7 @@ public class SyncManager : ISyncManager, IQueueObserver private readonly IBackOff _backOff; private readonly ISplitTask _startupTask; private readonly SplitQueue _streamingStatusQueue; - + private long _startSessionMs; private bool _streamingOff; @@ -184,7 +182,7 @@ private async Task StartupLogicAsync() if (_statusManager.IsDestroyed()) return; - _statusManager.SetReady(); + await _statusManager.SetReadyAsync(); clock.Stop(); _log.Debug($"Time until SDK ready: {clock.ElapsedMilliseconds} ms."); _telemetrySyncTask.RecordConfigInit(clock.ElapsedMilliseconds); diff --git a/src/Splitio/Services/Evaluator/Evaluator.cs b/src/Splitio/Services/Evaluator/Evaluator.cs index ee4040c47..d46958c78 100644 --- a/src/Splitio/Services/Evaluator/Evaluator.cs +++ b/src/Splitio/Services/Evaluator/Evaluator.cs @@ -3,7 +3,7 @@ using Splitio.Enums.Extensions; using Splitio.Services.Cache.Interfaces; using Splitio.Services.EngineEvaluator; -using Splitio.Services.Impressions.Classes; +using Splitio.Services.Impressions.Interfaces; using Splitio.Services.Logger; using Splitio.Services.Shared.Classes; using Splitio.Telemetry.Storages; @@ -23,12 +23,12 @@ public class Evaluator : IEvaluator private readonly ISplitter _splitter; private readonly IFeatureFlagCacheConsumer _featureFlagCacheConsumer; private readonly ITelemetryEvaluationProducer _telemetryEvaluationProducer; - private readonly FallbackTreatmentCalculator _fallbackTreatmentCalculator; + private readonly IFallbackTreatmentCalculator _fallbackTreatmentCalculator; public Evaluator(IFeatureFlagCacheConsumer featureFlagCache, ISplitter splitter, ITelemetryEvaluationProducer telemetryEvaluationProducer, - FallbackTreatmentCalculator fallbackTreatmentCalculator) + IFallbackTreatmentCalculator fallbackTreatmentCalculator) { _featureFlagCacheConsumer = featureFlagCache; _splitter = splitter; @@ -63,7 +63,7 @@ public List EvaluateFeatures(API method, Key key, List _log.Error($"{method}: Something went wrong evaluation feature: {feature}", e); _telemetryEvaluationProducer?.RecordException(method.ConvertToMethodEnum()); - treatmentsForFeatures.Add(Helper.checkFallbackTreatment(feature, Labels.Exception, true, _fallbackTreatmentCalculator)); + treatmentsForFeatures.Add(Helper.CheckFallbackTreatment(feature, Labels.Exception, true, _fallbackTreatmentCalculator)); } } @@ -137,7 +137,7 @@ public async Task> EvaluateFeaturesAsync(API method, Key k if (_telemetryEvaluationProducer != null) await _telemetryEvaluationProducer.RecordExceptionAsync(method.ConvertToMethodEnum()); - treatmentsForFeatures.Add(Helper.checkFallbackTreatment(feature, Labels.Exception, true, _fallbackTreatmentCalculator)); + treatmentsForFeatures.Add(Helper.CheckFallbackTreatment(feature, Labels.Exception, true, _fallbackTreatmentCalculator)); } } @@ -345,7 +345,7 @@ private TreatmentResult EvaluateFeatureException(Exception e, string featureName { _log.Error($"Exception caught getting treatment for feature flag: {featureName}", e); - return Helper.checkFallbackTreatment(featureName, Labels.Exception, true, _fallbackTreatmentCalculator); + return Helper.CheckFallbackTreatment(featureName, Labels.Exception, true, _fallbackTreatmentCalculator); } private List EvaluateFeaturesException(Exception e, List featureNames) @@ -356,7 +356,7 @@ private List EvaluateFeaturesException(Exception e, List IsValidByFlagTreatment(Dictionary byFlagTreatment, Enums.API method) + public Dictionary IsValidByFlagTreatment(Dictionary byFlagTreatment) { Dictionary result = new Dictionary(); foreach (var entry in byFlagTreatment) { - string featureName = new SplitNameValidator(_log).SplitNameIsValid(entry.Key, method).Value; + string featureName = new SplitNameValidator(_log).SplitNameIsValid(entry.Key, Enums.API.Split).Value; if (string.IsNullOrEmpty(featureName)) { continue; } FallbackTreatment fallbackTreatment = entry.Value; - string treatment = IsValidTreatment(fallbackTreatment.Treatment, method); + string treatment = IsValidTreatment(fallbackTreatment.Treatment); if (treatment != null) { result.Add(featureName, new FallbackTreatment(treatment, fallbackTreatment.Config)); diff --git a/src/Splitio/Services/InputValidation/Classes/PropertiesValidator.cs b/src/Splitio/Services/InputValidation/Classes/PropertiesValidator.cs index e1e7a29fd..2c47febb7 100644 --- a/src/Splitio/Services/InputValidation/Classes/PropertiesValidator.cs +++ b/src/Splitio/Services/InputValidation/Classes/PropertiesValidator.cs @@ -62,7 +62,7 @@ public PropertiesValidatorResult IsValid(Dictionary properties) }; } - private static bool IsNumeric(object value) + public static bool IsNumeric(object value) { if (value == null) return false; diff --git a/src/Splitio/Services/InputValidation/Interfaces/IFallbackTreatmentsValidator.cs b/src/Splitio/Services/InputValidation/Interfaces/IFallbackTreatmentsValidator.cs index 498f6681d..0fb71877a 100644 --- a/src/Splitio/Services/InputValidation/Interfaces/IFallbackTreatmentsValidator.cs +++ b/src/Splitio/Services/InputValidation/Interfaces/IFallbackTreatmentsValidator.cs @@ -5,6 +5,6 @@ namespace Splitio.Services.InputValidation.Interfaces { public interface IFallbackTreatmentsValidator { - FallbackTreatmentsConfiguration validate(FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration, Enums.API method); + FallbackTreatmentsConfiguration validate(FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration); } } \ No newline at end of file diff --git a/src/Splitio/Services/Shared/Classes/ClientExtensionService.cs b/src/Splitio/Services/Shared/Classes/ClientExtensionService.cs index 19cdf0246..5f938ab1e 100644 --- a/src/Splitio/Services/Shared/Classes/ClientExtensionService.cs +++ b/src/Splitio/Services/Shared/Classes/ClientExtensionService.cs @@ -4,7 +4,7 @@ using Splitio.Enums.Extensions; using Splitio.Services.Cache.Interfaces; using Splitio.Services.Filters; -using Splitio.Services.Impressions.Classes; +using Splitio.Services.Impressions.Interfaces; using Splitio.Services.InputValidation.Interfaces; using Splitio.Services.Logger; using Splitio.Services.Shared.Interfaces; @@ -27,7 +27,7 @@ public class ClientExtensionService : IClientExtensionService private readonly ITrafficTypeValidator _trafficTypeValidator; private readonly IFlagSetsValidator _flagSetsValidator; private readonly IFlagSetsFilter _flagSetsFilter; - private readonly FallbackTreatmentCalculator _fallbackTreatmentCalculator; + private readonly IFallbackTreatmentCalculator _fallbackTreatmentCalculator; public ClientExtensionService(IBlockUntilReadyService blockUntilReadyService, IStatusManager statusManager, @@ -39,7 +39,7 @@ public ClientExtensionService(IBlockUntilReadyService blockUntilReadyService, ITrafficTypeValidator trafficTypeValidator, IFlagSetsValidator flagSetsValidator, IFlagSetsFilter flagSetsFilter, - FallbackTreatmentCalculator fallbackTreatmentCalculator) + IFallbackTreatmentCalculator fallbackTreatmentCalculator) { _blockUntilReadyService = blockUntilReadyService; _statusManager = statusManager; @@ -162,7 +162,7 @@ public List ReturnControl(List featureFlagNames, string foreach (var item in featureFlagNames) { - toReturn.Add(Helper.checkFallbackTreatment(item, label, true, _fallbackTreatmentCalculator)); + toReturn.Add(Helper.CheckFallbackTreatment(item, label, true, _fallbackTreatmentCalculator)); } return toReturn; diff --git a/src/Splitio/Services/Shared/Classes/ConfigService.cs b/src/Splitio/Services/Shared/Classes/ConfigService.cs index 6e9a684e0..df2e7a587 100644 --- a/src/Splitio/Services/Shared/Classes/ConfigService.cs +++ b/src/Splitio/Services/Shared/Classes/ConfigService.cs @@ -19,6 +19,7 @@ public class ConfigService : IConfigService private readonly IWrapperAdapter _wrapperAdapter; private readonly IFlagSetsValidator _flagSetsValidator; private readonly ISdkMetadataValidator _sdkMetadataValidator; + private readonly FallbackTreatmentsValidator _fallbackTreatmentsValidator = new FallbackTreatmentsValidator(); public ConfigService(IWrapperAdapter wrapperAdapter, IFlagSetsValidator flagSetsValidator, @@ -59,6 +60,7 @@ public static LocalhostClientConfigurations ReadLocalhostConfig(ConfigurationOpt } localhostClientConfigurations.FileSync = fileSync; + localhostClientConfigurations.FallbackTreatments = new FallbackTreatmentsValidator().validate(config.FallbackTreatments); return localhostClientConfigurations; } @@ -124,7 +126,8 @@ public SelfRefreshingConfig ReadInMemoryConfig(ConfigurationOptions config) OnDemandFetchMaxRetries = 10, OnDemandFetchRetryDelayMs = 50, ProxyHost = config.ProxyHost, - ProxyPort = config.ProxyPort + ProxyPort = config.ProxyPort, + FallbackTreatments = baseConfig.FallbackTreatments }; selfRefreshingConfig.ImpressionsMode = config.ImpressionsMode ?? ImpressionsMode.Optimized; @@ -151,7 +154,8 @@ private BaseConfig ReadBaseConfig(ConfigurationOptions config, ConfigTypes type) UniqueKeysCacheMaxSize = 50000, ImpressionsCounterCacheMaxSize = 50000, FlagSetsFilter = flagSetsResult.FlagSets, - FlagSetsInvalid = flagSetsResult.Invalid + FlagSetsInvalid = flagSetsResult.Invalid, + FallbackTreatments = _fallbackTreatmentsValidator.validate(config.FallbackTreatments) }; } diff --git a/src/Splitio/Services/Shared/Classes/SelfRefreshingBlockUntilReadyService.cs b/src/Splitio/Services/Shared/Classes/SelfRefreshingBlockUntilReadyService.cs index fcc27eecb..f5e9315e9 100644 --- a/src/Splitio/Services/Shared/Classes/SelfRefreshingBlockUntilReadyService.cs +++ b/src/Splitio/Services/Shared/Classes/SelfRefreshingBlockUntilReadyService.cs @@ -3,6 +3,7 @@ using Splitio.Services.Shared.Interfaces; using Splitio.Telemetry.Storages; using System; +using System.Threading.Tasks; namespace Splitio.Services.Shared.Classes { diff --git a/src/Splitio/Services/Tasks/IInternalEventsTask.cs b/src/Splitio/Services/Tasks/IInternalEventsTask.cs new file mode 100644 index 000000000..47552f7e5 --- /dev/null +++ b/src/Splitio/Services/Tasks/IInternalEventsTask.cs @@ -0,0 +1,12 @@ +using Splitio.Domain; +using System.Threading.Tasks; + +namespace Splitio.Services.Tasks +{ + public interface IInternalEventsTask + { + Task AddToQueue(SdkInternalEvent sdkInternalEvent, EventMetadata eventMetadata); + void Start(); + void Stop(); + } +} diff --git a/src/Splitio/Services/Tasks/InternalEventsTask.cs b/src/Splitio/Services/Tasks/InternalEventsTask.cs new file mode 100644 index 000000000..411aa73b2 --- /dev/null +++ b/src/Splitio/Services/Tasks/InternalEventsTask.cs @@ -0,0 +1,64 @@ +using Splitio.Domain; +using Splitio.Services.Common; +using Splitio.Services.EventSource.Workers; +using Splitio.Services.Logger; +using Splitio.Services.Shared.Classes; +using System; +using System.Threading.Tasks; + +namespace Splitio.Services.Tasks +{ + public class InternalEventsTask : BaseWorker, IQueueObserver, IInternalEventsTask + { + private readonly IEventsManager _eventsManager; + private readonly ISplitLogger _logger = WrapperAdapter.Instance().GetLogger("InternalEventsTask"); + private readonly SplitQueue _queue; + + public InternalEventsTask(IEventsManager eventsManager, + SplitQueue internalEventsQueue) : base("InternalEventsTask", WrapperAdapter.Instance().GetLogger(typeof(InternalEventsTask))) + { + _eventsManager = eventsManager; + _queue = internalEventsQueue; + _queue.AddObserver(this); + } + + public async Task AddToQueue(SdkInternalEvent sdkInternalEvent, EventMetadata eventMetadata) + { + try + { + if (!_running) + { + _logger.Error("InternalEventTask Worker not running."); + return; + } + + _logger.Debug($"InternalEventTask: Add to queue: {sdkInternalEvent}"); + await _queue.EnqueueAsync(new SdkEventNotification(sdkInternalEvent, eventMetadata)); + } + catch (Exception ex) + { + _logger.Error($"InternalEventTask error AddToQueue: {ex.Message}"); + } + } + + public async Task Notify() + { + await Task.Run(() => + { + try + { + if (!_queue.TryDequeue(out SdkEventNotification sdkEventDto)) return; + + _logger.Debug($"InternalEventTask: SdkEvent dequeue: {sdkEventDto.SdkInternalEvent}"); + _eventsManager.NotifyInternalEvent(sdkEventDto.SdkInternalEvent, sdkEventDto.EventMetadata); + } + catch (Exception ex) + { + if (ex is OperationCanceledException) return; + + _logger.Debug($"InternalEventTask worker Execute exception", ex); + } + }); + } + } +} diff --git a/src/Splitio/Splitio.csproj b/src/Splitio/Splitio.csproj index f7d091495..953026425 100644 --- a/src/Splitio/Splitio.csproj +++ b/src/Splitio/Splitio.csproj @@ -7,7 +7,7 @@ false false false - 7.12.0 + 7.13.0-rc1 Splitio true Splitio.snk diff --git a/src/Splitio/Util/Helper.cs b/src/Splitio/Util/Helper.cs index 84dc8dda1..bcaa4557a 100644 --- a/src/Splitio/Util/Helper.cs +++ b/src/Splitio/Util/Helper.cs @@ -1,6 +1,6 @@ using Splitio.CommonLibraries; using Splitio.Domain; -using Splitio.Services.Impressions.Classes; +using Splitio.Services.Impressions.Interfaces; using Splitio.Services.Logger; using Splitio.Telemetry.Domain.Enums; using Splitio.Telemetry.Storages; @@ -62,7 +62,7 @@ public static List> ChunkBy(List source, int chunkSize) .ToList(); } - public static TreatmentResult checkFallbackTreatment(string featureName, string label, bool exception, FallbackTreatmentCalculator fallbackTreatmentCalculator) + public static TreatmentResult CheckFallbackTreatment(string featureName, string label, bool exception, IFallbackTreatmentCalculator fallbackTreatmentCalculator) { FallbackTreatment fallbackTreatment = fallbackTreatmentCalculator.resolve(featureName, label); return new TreatmentResult(featureName, @@ -70,12 +70,12 @@ public static TreatmentResult checkFallbackTreatment(string featureName, string fallbackTreatment.Treatment, false, null, - getFallbackConfig(fallbackTreatment), + GetFallbackConfig(fallbackTreatment), exception ); } - public static string getFallbackConfig(FallbackTreatment fallbackTreatment) + public static string GetFallbackConfig(FallbackTreatment fallbackTreatment) { if (fallbackTreatment.Config != null) { diff --git a/tests/Splitio-tests/Integration Tests/BaseLocalhostClientTests.cs b/tests/Splitio-tests/Integration Tests/BaseLocalhostClientTests.cs index 4f035f950..263133942 100644 --- a/tests/Splitio-tests/Integration Tests/BaseLocalhostClientTests.cs +++ b/tests/Splitio-tests/Integration Tests/BaseLocalhostClientTests.cs @@ -1,9 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Splitio.Domain; -using Splitio.Redis.Services.Client.Classes; using Splitio.Services.Client.Classes; using Splitio.Services.Impressions.Classes; -using Splitio.Telemetry.Domain; using System; using System.Collections.Generic; using System.IO; @@ -23,14 +21,12 @@ public abstract class BaseLocalhostClientTests { private readonly string rootFilePath; private readonly string _mode; - private readonly FallbackTreatmentCalculator _fallbackTreatmentCalculator; public BaseLocalhostClientTests(string mode) { // This line is to clean the warnings. rootFilePath = string.Empty; _mode = mode; - _fallbackTreatmentCalculator = new FallbackTreatmentCalculator(new FallbackTreatmentsConfiguration()); #if NET_LATEST rootFilePath = @"Resources\"; @@ -42,7 +38,7 @@ public async Task GetTreatmentAsync() { // Arrange. var config = GetConfiguration($"{rootFilePath}test.splits"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -81,7 +77,7 @@ public void GetTreatmentSuccessfully() { //Arrange var config = GetConfiguration($"{rootFilePath}test.splits"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -105,7 +101,7 @@ public void GetTreatmentSuccessfullyWhenUpdatingSplitsFile() // Arrange var filePath = $"{rootFilePath}test2-{_mode}.splits"; var config = GetConfiguration(filePath); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -148,7 +144,7 @@ public void GetTreatmentSuccessfullyWhenUpdatingSplitsFileSameFile() Thread.Sleep(1000); var config = GetConfiguration(filePath); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -171,7 +167,7 @@ public void ClientDestroySuccessfully() { //Arrange var config = GetConfiguration($"{rootFilePath}test.splits"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -193,7 +189,7 @@ public void GetTreatment_WhenIsYamlFile_Successfully() { //Arrange var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -225,7 +221,7 @@ public void GetTreatmentWithConfig_WhenIsYamlFile_Successfully() { //Arrange var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -264,7 +260,7 @@ public void GetTreatment_WhenIsYmlFile_Successfully() { //Arrange var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -296,7 +292,7 @@ public void GetTreatmentWithConfig_WhenIsYmlFile_Successfully() { //Arrange var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -335,7 +331,7 @@ public void GetTreatments_WhenIsYamlFile_Successfully() { //Arrange var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -372,7 +368,7 @@ public void GetTreatmentsWithConfig_WhenIsYamlFile_Successfully() { //Arrange var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -423,7 +419,7 @@ public void GetTreatments_WhenIsYmlFile_Successfully() { //Arrange var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -460,7 +456,7 @@ public void GetTreatmentsWithConfig_WhenIsYmlFile_Successfully() { //Arrange var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, _fallbackTreatmentCalculator); + var client = new LocalhostClient(config); client.BlockUntilReady(1000); @@ -511,10 +507,10 @@ public void FallbackTreatments_WhenFeatureDoesNotExist() { var features = new List { "testing_split_on", "feature", "feature2" }; FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global", "\"prop\":\"global\""), new Dictionary() { { "feature", new FallbackTreatment("off-local", "\"prop\":\"local\"") } }); - FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); var config = GetConfiguration($"{rootFilePath}split.yml"); - var client = new LocalhostClient(config, fallbackTreatmentCalculator); + config.FallbackTreatments = fallbackTreatmentsConfiguration; + var client = new LocalhostClient(config); client.BlockUntilReady(1000); diff --git a/tests/Splitio-tests/Integration Tests/InMemoryClientTests.cs b/tests/Splitio-tests/Integration Tests/InMemoryClientTests.cs index 5732ce6c3..cf591e65e 100644 --- a/tests/Splitio-tests/Integration Tests/InMemoryClientTests.cs +++ b/tests/Splitio-tests/Integration Tests/InMemoryClientTests.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json.Serialization; using Splitio.Domain; using Splitio.Services.Client.Classes; +using Splitio.Services.Common; using Splitio.Services.Impressions.Classes; namespace Splitio_Tests.Integration_Tests @@ -11,13 +12,11 @@ namespace Splitio_Tests.Integration_Tests public class InMemoryClientTests { private readonly string rootFilePath; - private readonly FallbackTreatmentCalculator _fallbackTreatmentCalculator; public InMemoryClientTests() { // This line is to clean the warnings. rootFilePath = string.Empty; - _fallbackTreatmentCalculator = new FallbackTreatmentCalculator(new FallbackTreatmentsConfiguration()); #if NET_LATEST rootFilePath = @"Resources\"; @@ -36,7 +35,7 @@ public void OverridingJsonConvertSettingSnakeCaseNamingStrategy() NamingStrategy = new SnakeCaseNamingStrategy() } }; - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); //Act diff --git a/tests/Splitio-tests/Integration Tests/JSONFileClientTests.cs b/tests/Splitio-tests/Integration Tests/JSONFileClientTests.cs index efc6e8b71..5ad0ea235 100644 --- a/tests/Splitio-tests/Integration Tests/JSONFileClientTests.cs +++ b/tests/Splitio-tests/Integration Tests/JSONFileClientTests.cs @@ -3,6 +3,7 @@ using Splitio.Domain; using Splitio.Services.Cache.Interfaces; using Splitio.Services.Client.Classes; +using Splitio.Services.Common; using Splitio.Services.Events.Interfaces; using Splitio.Services.Impressions.Classes; using Splitio.Services.Impressions.Interfaces; @@ -18,13 +19,11 @@ namespace Splitio_Tests.Integration_Tests public class JSONFileClientTests { private readonly string rootFilePath; - private readonly FallbackTreatmentCalculator _fallbackTreatmentCalculator; public JSONFileClientTests() { // This line is to clean the warnings. rootFilePath = string.Empty; - _fallbackTreatmentCalculator = new FallbackTreatmentCalculator(new FallbackTreatmentsConfiguration()); #if NET_LATEST rootFilePath = @"Resources\"; @@ -37,7 +36,7 @@ public JSONFileClientTests() public void ExecuteGetTreatmentOnFailedParsingSplitShouldReturnControl() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); //Act var result = client.GetTreatment("test", "fail", null); @@ -52,7 +51,7 @@ public void ExecuteGetTreatmentOnFailedParsingSplitShouldReturnControl() public void ExecuteGetTreatmentOnFailedParsingSplitShouldNotAffectOtherSplits() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -69,7 +68,7 @@ public void ExecuteGetTreatmentOnFailedParsingSplitShouldNotAffectOtherSplits() public void ExecuteGetTreatmentOnDeletedSplitShouldReturnControl() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -97,7 +96,7 @@ public void ExecuteGetTreatmentOnExceptionShouldReturnControl() .Setup(x => x.GetSplit(It.IsAny())) .Throws(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, null, splitCacheMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions(), featureFlagCacheInstance: splitCacheMock.Object, impressionsLog: impressionsLogMock.Object); //Act var result = client.GetTreatment("test", "asd", null); @@ -113,7 +112,7 @@ public void ExecuteGetTreatmentOnExceptionShouldReturnControl() public void ExecuteGetTreatmentOnRemovedUserFromSegmentShouldReturnOff() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", $"{rootFilePath}segment_payed.json", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", $"{rootFilePath}segment_payed.json", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -135,7 +134,7 @@ public void ExecuteGetTreatmentOnRemovedUserFromSegmentShouldReturnOff() public void ExecuteGetTreatmentOnSplitWithOnOffOnPartition() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -159,7 +158,7 @@ public void ExecuteGetTreatmentOnSplitWithOnOffOnPartition() public void ExecuteGetTreatmentOnSplitWithTrafficAllocation() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -183,7 +182,7 @@ public void ExecuteGetTreatmentOnSplitWithTrafficAllocation() public void ExecuteGetTreatmentOnSplitWithTrafficAllocationWhenAllocationIsDifferentThan100() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -207,7 +206,7 @@ public void ExecuteGetTreatmentOnSplitWithTrafficAllocationWhenAllocationIsDiffe public void ExecuteGetTreatmentOnSplitWithTrafficAllocationWhenAllocationIs1ReturnsRolloutTreatment() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_7.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_7.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -223,7 +222,7 @@ public void ExecuteGetTreatmentOnSplitWithTrafficAllocationWhenAllocationIs1Retu public void ExecuteGetTreatmentOnSplitWithTrafficAllocationWhenAllocationIs1ReturnsDefaultTreatment() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_7.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_7.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -239,7 +238,7 @@ public void ExecuteGetTreatmentOnSplitWithTrafficAllocationWhenAllocationIs1Retu public void ExecuteGetTreatmentOnSplitWithSegmentNotInitialized() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -259,7 +258,7 @@ public void ExecuteGetTreatmentAndLogLabelKilled() { //Arrange var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions(), impressionsLog: impressionsLogMock.Object); client.BlockUntilReady(1000); @@ -283,7 +282,7 @@ public void ExecuteGetTreatmentAndLogLabelNoConditionMatched() { //Arrange var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions(), impressionsLog: impressionsLogMock.Object); client.BlockUntilReady(1000); @@ -307,7 +306,7 @@ public void ExecuteGetTreatmentAndLogLabelSplitNotFound() { //Arrange var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); //Act client.RemoveSplitFromCache("asd"); @@ -336,7 +335,7 @@ public void ExecuteGetTreatmentAndLogLabelException() .Setup(x => x.GetSplit(It.IsAny())) .Throws(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, featureFlagCacheInstance: splitCacheMock.Object, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions { ImpressionsMode = ImpressionsMode.Debug }, impressionsLog: impressionsLogMock.Object, featureFlagCacheInstance: splitCacheMock.Object); client.BlockUntilReady(1000); @@ -360,7 +359,7 @@ public void ExecuteGetTreatmentAndLogLabelTrafficAllocationFailed() { //Arrange var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", new ConfigurationOptions(), impressionsLog: impressionsLogMock.Object); client.BlockUntilReady(1000); @@ -383,7 +382,7 @@ public void ExecuteGetTreatmentAndLogLabelForTreatment() { //Arrange var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions(), impressionsLog: impressionsLogMock.Object); client.BlockUntilReady(1000); @@ -406,7 +405,7 @@ public void ExecuteGetTreatmentAndLogLabelForTreatment() public void ExecuteGetTreatmentWhenUnknownMatcherIsIncluded() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); //Act var result = client.GetTreatment("xs", "Unknown_Matcher", null); @@ -421,7 +420,7 @@ public void ExecuteGetTreatmentAndNotLogLabelForTreatmentIfLabelsNotEnabled() { //Arrange var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object, isLabelsEnabled: false); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions(), impressionsLog: impressionsLogMock.Object, isLabelsEnabled: false); client.BlockUntilReady(1000); @@ -444,7 +443,7 @@ public void ExecuteGetTreatmentAndLogLabelAndBucketingKeyForTreatment() { //Arrange var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions { ImpressionsMode = ImpressionsMode.Debug }, impressionsLog: impressionsLogMock.Object); client.BlockUntilReady(1000); @@ -468,7 +467,7 @@ public void ExecuteGetTreatmentAndLogLabelAndBucketingKeyForTreatment() public void ExecuteGetTreatmentWithBooleanAttribute() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_4.json", "", new ConfigurationOptions()); var attributes = new Dictionary { @@ -490,7 +489,7 @@ public void ExecuteGetTreatmentWithBooleanAttribute() public void ExecuteGetTreatmentWithSetMatcherReturnsOff() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", new ConfigurationOptions()); var attributes = new Dictionary { @@ -512,7 +511,7 @@ public void ExecuteGetTreatmentWithSetMatcherReturnsOff() public void ExecuteGetTreatmentWithSetMatcherReturnsOn() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", new ConfigurationOptions()); var attributes = new Dictionary { @@ -534,7 +533,7 @@ public void ExecuteGetTreatmentWithSetMatcherReturnsOn() public void ExecuteGetTreatmentWithStringMatcherReturnsOff() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", new ConfigurationOptions()); var attributes = new Dictionary { @@ -556,7 +555,7 @@ public void ExecuteGetTreatmentWithStringMatcherReturnsOff() public void ExecuteGetTreatmentWithStringMatcherReturnsOn() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", new ConfigurationOptions()); var attributes = new Dictionary { @@ -578,7 +577,7 @@ public void ExecuteGetTreatmentWithStringMatcherReturnsOn() public void ExecuteGetTreatmentWithDependencyMatcherReturnsOn() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -595,7 +594,7 @@ public void ExecuteGetTreatmentWithDependencyMatcherReturnsOn() public void ExecuteGetTreatmentWithDependencyMatcherReturnsOff() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -613,7 +612,7 @@ public void ExecuteGetTreatmentWithDependencyMatcherImpressionOnChild() { //Arrange var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", new ConfigurationOptions(),impressionsLog: impressionsLogMock.Object); client.BlockUntilReady(1000); @@ -631,7 +630,7 @@ public void GetTreatment_WhenNameDoesntExist_DontLogImpression() { // Arrange. var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); var splitName = "not_exist"; client.BlockUntilReady(1000); @@ -649,7 +648,7 @@ public void GetTreatment_WhenNameDoesntExist_DontLogImpression() public void GetTreatment_WithoutBlockUntiltReady_ReturnsOff() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); // Act. var result = client.GetTreatment("key", "anding"); @@ -665,7 +664,7 @@ public void GetTreatment_WithoutBlockUntiltReady_ReturnsOff() public void ExecuteGetTreatments() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); List features = new List { "fail", @@ -696,7 +695,7 @@ public void ExecuteGetTreatments() public void ExecuteGetTreatmentsWithBucketing() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); List features = new List { "fail", @@ -728,7 +727,7 @@ public void ExecuteGetTreatmentsWithBucketing() public void ExecuteGetTreatmentsWithDependencyMatcherReturnsOn() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -751,7 +750,7 @@ public void ExecuteGetTreatmentsWithDependencyMatcherReturnsOn() public void ExecuteGetTreatmentsWithDependencyMatcherWithAttributesReturnsOn() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_6.json", "", new ConfigurationOptions()); client.BlockUntilReady(1000); @@ -779,7 +778,7 @@ public void GetTreatments_WhenNameDoesntExist_DontLogImpression() { // Arrange. var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); var splitNames = new List { "not_exist", "not_exist_1" }; client.BlockUntilReady(1000); @@ -801,7 +800,7 @@ public void GetTreatments_WhenNameDoesntExist_DontLogImpression() public void GetTreatments_WithoutBlockUntiltReady_ReturnsEmptyList() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); // Act. var result = client.GetTreatments("key", new List()); @@ -815,7 +814,7 @@ public void GetTreatments_WithoutBlockUntiltReady_ReturnsEmptyList() public void GetTreatments_WithoutBlockUntiltReady_ReturnsTreatments() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); // Act. var result = client.GetTreatments("key", new List { "anding", "in_ten_keys" }); @@ -833,7 +832,7 @@ public void GetTreatments_WithoutBlockUntiltReady_ReturnsTreatments() public void GetTreatments_WhenClientIsReadyAndFeaturesIsEmpty_ReturnsEmptyList() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); client.BlockUntilReady(100); // Act. @@ -850,7 +849,7 @@ public void GetTreatments_WhenClientIsReadyAndFeaturesIsEmpty_ReturnsEmptyList() public void DestroySucessfully() { //Arrange - var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_5.json", "", new ConfigurationOptions()); var attributes = new Dictionary { @@ -886,7 +885,7 @@ public void Track_WhenClientIsNotReady_ReturnsTrue() // Arrange. var trafficTypeValidator = new Mock(); var eventLog = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, trafficTypeValidator: trafficTypeValidator.Object, eventsLog: eventLog.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions(), trafficTypeValidator: trafficTypeValidator.Object, eventsLog: eventLog.Object); trafficTypeValidator .Setup(mock => mock.IsValid(It.IsAny(), It.IsAny())) @@ -907,7 +906,7 @@ public void GetTreatmentWithConfig_WhenNameDoesntExist_DontLogImpression() { // Arrange. var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); var splitName = "not_exist"; client.BlockUntilReady(1000); @@ -926,7 +925,7 @@ public void GetTreatmentWithConfig_WhenNameDoesntExist_DontLogImpression() public void GetTreatmentWithConfig_WithoutBlockUntiltReady_ReturnsOff() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); // Act. var result = client.GetTreatmentWithConfig("key", "anding"); @@ -944,7 +943,7 @@ public void GetTreatmentsWithConfig_WhenNameDoesntExist_DontLogImpression() { // Arrange. var impressionsLogMock = new Mock(); - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator, impressionsLog: impressionsLogMock.Object); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); var splitNames = new List { "not_exist", "not_exist_1" }; client.BlockUntilReady(1000); @@ -967,7 +966,7 @@ public void GetTreatmentsWithConfig_WhenNameDoesntExist_DontLogImpression() public void GetTreatmentsWithConfig_WithoutBlockUntiltReady_ReturnsEmptyList() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); // Act. var result = client.GetTreatmentsWithConfig("anding", new List()); @@ -981,7 +980,7 @@ public void GetTreatmentsWithConfig_WithoutBlockUntiltReady_ReturnsEmptyList() public void GetTreatmentsWithConfig_WithoutBlockUntiltReady_ReturnsTreatments() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); // Act. var result = client.GetTreatmentsWithConfig("key", new List { "anding", "whitelisting_elements" }); @@ -1001,7 +1000,7 @@ public void GetTreatmentsWithConfig_WithoutBlockUntiltReady_ReturnsTreatments() public void GetTreatmentsWithConfig_WhenClientIsReadyAndFeaturesIsEmpty_ReturnsEmptyList() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); client.BlockUntilReady(100); // Act. @@ -1018,7 +1017,7 @@ public void GetTreatmentsWithConfig_WhenClientIsReadyAndFeaturesIsEmpty_ReturnsE public void Split_Manager_WhenNameDoesntExist_ReturnsNull() { // Arrange. - var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", _fallbackTreatmentCalculator); + var client = new JSONFileClient($"{rootFilePath}splits_staging_3.json", "", new ConfigurationOptions()); var manager = client.GetSplitManager(); var splitName = "not_exist"; diff --git a/tests/Splitio-tests/Integration Tests/RedisClientTests.cs b/tests/Splitio-tests/Integration Tests/RedisClientTests.cs index 84ebcbd79..5b7ea4c79 100644 --- a/tests/Splitio-tests/Integration Tests/RedisClientTests.cs +++ b/tests/Splitio-tests/Integration Tests/RedisClientTests.cs @@ -4,6 +4,7 @@ using Splitio.Redis.Services.Client.Classes; using Splitio.Redis.Services.Domain; using Splitio.Services.Client.Classes; +using Splitio.Services.Common; using Splitio.Services.Impressions.Classes; using Splitio.Tests.Common.Resources; using Splitio_Tests.Resources; @@ -25,6 +26,7 @@ public class RedisClientTests private ConfigurationOptions config; private RedisAdapterForTests _redisAdapter; private FallbackTreatmentCalculator _fallbackTreatmentCalculator; + private EventsManager _eventsManager; [TestInitialize] public void Initialization() @@ -38,6 +40,7 @@ public void Initialization() UserPrefix = _prefix }; _fallbackTreatmentCalculator = new FallbackTreatmentCalculator(new FallbackTreatmentsConfiguration()); + _eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); config = new ConfigurationOptions { CacheAdapterConfig = cacheAdapterConfig, @@ -64,7 +67,7 @@ public void Initialization() public void GetTreatment_WhenFeatureExists_ReturnsOn() { //Arrange - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); client.BlockUntilReady(5000); @@ -75,7 +78,7 @@ public void GetTreatment_WhenFeatureExists_ReturnsOn() Assert.IsNotNull(result); Assert.AreEqual("on", result); - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); @@ -91,7 +94,7 @@ public void GetTreatment_WhenFeatureExists_ReturnsOn() public void GetTreatment_WhenFeatureExists_ReturnsOff() { //Arrange - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); client.BlockUntilReady(5000); @@ -102,7 +105,7 @@ public void GetTreatment_WhenFeatureExists_ReturnsOff() Assert.IsNotNull(result); Assert.AreEqual("off", result); - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); result = client2.GetTreatment("test", "always_off", null); @@ -116,7 +119,7 @@ public void GetTreatment_WhenFeatureExists_ReturnsOff() public void GetTreatment_WhenFeatureDoenstExist_ReturnsControl() { //Arrange - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); client.BlockUntilReady(5000); //Act @@ -126,7 +129,7 @@ public void GetTreatment_WhenFeatureDoenstExist_ReturnsControl() Assert.IsNotNull(result); Assert.AreEqual("control", result); - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); result = client2.GetTreatment("test", "always_control", null); @@ -145,7 +148,7 @@ public void GetTreatments_WhenFeaturesExists_ReturnsOnOff() var features = new List { alwaysOn, alwaysOff }; - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); client.BlockUntilReady(5000); @@ -159,7 +162,7 @@ public void GetTreatments_WhenFeaturesExists_ReturnsOnOff() var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), - API_KEY, _fallbackTreatmentCalculator); + API_KEY); client2.BlockUntilReady(5000); result = client2.GetTreatments("test", features, null); @@ -180,7 +183,7 @@ public void GetTreatments_WhenOneFeatureDoenstExist_ReturnsOnOffControl() var features = new List { alwaysOn, alwaysOff, alwaysControl }; - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); client.BlockUntilReady(5000); @@ -193,8 +196,7 @@ public void GetTreatments_WhenOneFeatureDoenstExist_ReturnsOnOffControl() Assert.AreEqual("on", result[alwaysOn]); Assert.AreEqual("control", result[alwaysControl]); - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), - API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); result = client2.GetTreatments("test", features, null); @@ -210,7 +212,7 @@ public void GetTreatments_WhenOneFeatureDoenstExist_ReturnsOnOffControl() public void GetTreatmentsWithConfig_WhenClientIsNotReady_ReturnsControl() { // Arrange. - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); // Act. var result = client.GetTreatmentsWithConfig("key", new List()); @@ -222,8 +224,7 @@ public void GetTreatmentsWithConfig_WhenClientIsNotReady_ReturnsControl() Assert.IsNull(res.Value.Config); } - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), -API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); result = client2.GetTreatmentsWithConfig("key", new List()); @@ -240,7 +241,7 @@ public void GetTreatmentsWithConfig_WhenClientIsNotReady_ReturnsControl() public void GetTreatmentWithConfig_WhenClientIsNotReady_ReturnsControl() { // Arrange. - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); // Act. var result = client.GetTreatmentWithConfig("key", string.Empty); @@ -249,8 +250,7 @@ public void GetTreatmentWithConfig_WhenClientIsNotReady_ReturnsControl() Assert.AreEqual("control", result.Treatment); Assert.IsNull(result.Config); - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), -API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); result = client2.GetTreatmentWithConfig("key", string.Empty); @@ -264,7 +264,7 @@ public void GetTreatmentWithConfig_WhenClientIsNotReady_ReturnsControl() public void GetTreatment_WhenClientIsNotReady_ReturnsControl() { // Arrange. - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); // Act. var result = client.GetTreatment("key", string.Empty); @@ -272,8 +272,7 @@ public void GetTreatment_WhenClientIsNotReady_ReturnsControl() // Assert. Assert.AreEqual("control", result); - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), -API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); result = client2.GetTreatment("key", string.Empty); @@ -287,7 +286,7 @@ public void GetTreatments_WhenClientIsNotReady_ReturnsControl() { // Arrange. config.CacheAdapterConfig.Host = "fake-host"; - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); // Act. var result = client.GetTreatments("key", new List()); @@ -298,7 +297,7 @@ public void GetTreatments_WhenClientIsNotReady_ReturnsControl() Assert.AreEqual("control", res.Value); } - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); result = client2.GetTreatments("key", new List()); @@ -314,7 +313,7 @@ public void GetTreatments_WhenClientIsNotReady_ReturnsControl() public void Track_WhenClientIsNotReady_ReturnsTrue() { // Arrange. - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); // Act. var result = client.Track("key", "traffic_type", "event_type"); @@ -322,8 +321,7 @@ public void Track_WhenClientIsNotReady_ReturnsTrue() // Assert. Assert.IsTrue(result); - var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), -API_KEY, _fallbackTreatmentCalculator); + var client2 = new RedisClient(GetRedisClusterConfigurationOptions(), API_KEY); client2.BlockUntilReady(5000); // Act. @@ -340,9 +338,9 @@ public void FallbackTreatments_WhenFeatureDoesNotExist() var features = new List { alwaysOn, "feature", alwaysControl }; FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global", "\"prop\":\"global\""), new Dictionary() { { "feature", new FallbackTreatment("off-local", "\"prop\":\"local\"") } }); - FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); + config.FallbackTreatments = fallbackTreatmentsConfiguration; - var client = new RedisClient(config, API_KEY, fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); client.BlockUntilReady(5000); @@ -377,10 +375,10 @@ public void FallbackTreatments_WhenClientNotReady() var features = new List { "feature" }; FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global", "\"prop\":\"global\""), new Dictionary() { { "feature", new FallbackTreatment("off-local", "\"prop\":\"local\"") } }); - FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); config.CacheAdapterConfig.Host = "fake-host"; - var client = new RedisClient(config, API_KEY, fallbackTreatmentCalculator); + config.FallbackTreatments = fallbackTreatmentsConfiguration; + var client = new RedisClient(config, API_KEY); //Act var result = client.GetTreatmentsWithConfig("test", features, null); @@ -400,9 +398,9 @@ public void FallbackTreatments_WhenExceptionOccurrs() var features = new List { "always_off" }; FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global", "\"prop\":\"global\""), new Dictionary() { { "feature", new FallbackTreatment("off-local", "\"prop\":\"local\"") } }); - FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); + config.FallbackTreatments = fallbackTreatmentsConfiguration; - var client = new RedisClient(config, API_KEY, fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); client.BlockUntilReady(5000); @@ -431,7 +429,7 @@ public void FallbackTreatments_WhenExceptionOccurrs() public void Destroy() { //Arrange - var client = new RedisClient(config, API_KEY, _fallbackTreatmentCalculator); + var client = new RedisClient(config, API_KEY); client.BlockUntilReady(5000); //Act diff --git a/tests/Splitio-tests/Integration Tests/SelfRefreshingSegmentFetcherTests.cs b/tests/Splitio-tests/Integration Tests/SelfRefreshingSegmentFetcherTests.cs index 10cf92515..c06d2d696 100644 --- a/tests/Splitio-tests/Integration Tests/SelfRefreshingSegmentFetcherTests.cs +++ b/tests/Splitio-tests/Integration Tests/SelfRefreshingSegmentFetcherTests.cs @@ -1,7 +1,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Splitio.Domain; using Splitio.Services.Cache.Classes; +using Splitio.Services.Common; using Splitio.Services.SegmentFetcher.Classes; +using Splitio.Services.Tasks; using System.Collections.Concurrent; namespace Splitio_Tests.Integration_Tests @@ -26,7 +28,9 @@ public SelfRefreshingSegmentFetcherTests() public void ExecuteGetSuccessfulWithResultsFromJSONFile() { //Arrange - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + var eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new Splitio.Services.Shared.Classes.SplitQueue()); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask); var segmentFetcher = new JSONFileSegmentFetcher($"{rootFilePath}segment_payed.json", segmentCache); diff --git a/tests/Splitio-tests/Integration Tests/TargetingRulesFetcherTests.cs b/tests/Splitio-tests/Integration Tests/TargetingRulesFetcherTests.cs index 15c1b6740..a1fdde683 100644 --- a/tests/Splitio-tests/Integration Tests/TargetingRulesFetcherTests.cs +++ b/tests/Splitio-tests/Integration Tests/TargetingRulesFetcherTests.cs @@ -38,14 +38,16 @@ public TargetingRulesFetcherTests() public async Task ExecuteGetSuccessfulWithResultsFromJSONFile() { //Arrange - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); - var rbsCache = new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary()); + var eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask); + var rbsCache = new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary(), internalEventsTask); var segmentFetcher = new JSONFileSegmentFetcher($"{rootFilePath}segment_payed.json", segmentCache); var splitParser = new FeatureFlagParser(segmentCache, segmentFetcher); var splitChangeFetcher = new JSONFileSplitChangeFetcher($"{rootFilePath}splits_staging.json"); var flagSetsFilter = new FlagSetsFilter(new HashSet()); - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), flagSetsFilter); - var gates = new InMemoryReadinessGatesCache(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), flagSetsFilter, internalEventsTask); + var gates = new InMemoryReadinessGatesCache(internalEventsTask); var taskManager = new TasksManager(gates); var task = taskManager.NewPeriodicTask(Splitio.Enums.Task.FeatureFlagsFetcher, 250); var featureFlagSyncService = new FeatureFlagUpdater(splitParser, splitCache, flagSetsFilter, rbsCache); @@ -83,14 +85,16 @@ public async Task ExecuteGetSuccessfulWithResultsFromJSONFile() public async Task ExecuteGetSuccessfulWithResultsFromJSONFileIncludingTrafficAllocation() { //Arrange - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); - var rbsCache = new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary()); + var eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask); + var rbsCache = new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary(), internalEventsTask); var segmentFetcher = new JSONFileSegmentFetcher($"{rootFilePath}segment_payed.json", segmentCache); var splitParser = new FeatureFlagParser(segmentCache, segmentFetcher); var splitChangeFetcher = new JSONFileSplitChangeFetcher($"{rootFilePath}splits_staging_4.json"); var flagSetsFilter = new FlagSetsFilter(new HashSet()); - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), flagSetsFilter); - var gates = new InMemoryReadinessGatesCache(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), flagSetsFilter, internalEventsTask); + var gates = new InMemoryReadinessGatesCache(internalEventsTask); var taskManager = new TasksManager(gates); var task = taskManager.NewPeriodicTask(Splitio.Enums.Task.FeatureFlagsFetcher, 250); var featureFlagSyncService = new FeatureFlagUpdater(splitParser, splitCache, flagSetsFilter, rbsCache); @@ -139,17 +143,19 @@ public async Task ExecuteGetWithoutResults() var apiSplitChangeFetcher = new ApiSplitChangeFetcher(sdkApiClient); var sdkSegmentApiClient = new SegmentSdkApiClient(httpClient, telemetryStorage, baseUrl); var apiSegmentChangeFetcher = new ApiSegmentChangeFetcher(sdkSegmentApiClient); - var gates = new InMemoryReadinessGatesCache(); - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + var eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + var gates = new InMemoryReadinessGatesCache(internalEventsTask); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask); var segmentsQueue = new SplitQueue(); var taskManager = new TasksManager(gates); var worker = new SegmentTaskWorker(4, segmentsQueue); segmentsQueue.AddObserver(worker); var segmentsTask = taskManager.NewPeriodicTask(Splitio.Enums.Task.SegmentsFetcher, 3000); var segmentFetcher = new SelfRefreshingSegmentFetcher(apiSegmentChangeFetcher, segmentCache, segmentsQueue, segmentsTask, gates); - var rbsCache = new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary()); + var rbsCache = new InMemoryRuleBasedSegmentCache(new ConcurrentDictionary(), internalEventsTask); var splitParser = new FeatureFlagParser(segmentCache, segmentFetcher); - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), flagSetsFilter); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), flagSetsFilter, internalEventsTask); var task = taskManager.NewPeriodicTask(Splitio.Enums.Task.FeatureFlagsFetcher, 3000); var featureFlagSyncService = new FeatureFlagUpdater(splitParser, splitCache, flagSetsFilter, rbsCache); var rbsParser = new RuleBasedSegmentParser(segmentCache, segmentFetcher); diff --git a/tests/Splitio-tests/Unit Tests/Cache/InMemory/RuleBasedSegmentCacheTests.cs b/tests/Splitio-tests/Unit Tests/Cache/InMemory/RuleBasedSegmentCacheTests.cs index 763ac6ebe..5a3a06af9 100644 --- a/tests/Splitio-tests/Unit Tests/Cache/InMemory/RuleBasedSegmentCacheTests.cs +++ b/tests/Splitio-tests/Unit Tests/Cache/InMemory/RuleBasedSegmentCacheTests.cs @@ -1,8 +1,13 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Splitio.Domain; using Splitio.Services.Cache.Classes; +using Splitio.Services.Common; +using Splitio.Services.Shared.Classes; +using Splitio.Services.Tasks; +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Splitio_Tests.Unit_Tests.Cache.InMemory @@ -11,12 +16,21 @@ namespace Splitio_Tests.Unit_Tests.Cache.InMemory public class RuleBasedSegmentCacheTests { private InMemoryRuleBasedSegmentCache _segmentCache; + private EventsManager _eventsManager; + private bool SdkUpdateFlag = false; + private EventMetadata eMetadata = null; + private InternalEventsTask _internalEventsTask; + public event EventHandler SdkUpdate; + public event EventHandler SdkReady; [TestInitialize] public void Setup() { var cache = new ConcurrentDictionary(); - _segmentCache = new InMemoryRuleBasedSegmentCache(cache); + _eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + _internalEventsTask = new InternalEventsTask(_eventsManager, new SplitQueue()); + _internalEventsTask.Start(); + _segmentCache = new InMemoryRuleBasedSegmentCache(cache, _internalEventsTask); } [TestMethod] @@ -142,5 +156,51 @@ public void Contains_ShouldReturnTrue() Assert.IsFalse(_segmentCache.Contains(new List { "segment1", "segment3" })); Assert.IsTrue(_segmentCache.Contains(new List { "segment1", "segment2" })); } + + [TestMethod] + public void Update_ShouldNotifyEvent() + { + // Arrange + var segmentToAdd = new RuleBasedSegment { Name = "segment-to-add" }; + var segmentToRemove = new RuleBasedSegment { Name = "segment-to-remove" }; + var till = 67890; + var toNotify = new List { { "segment-to-add" }, { "segment-to-remove" } }; + SdkUpdate += sdkUpdate_callback; + _eventsManager.Register(SdkEvent.SdkUpdate, TriggerSdkUpdate); + _eventsManager.Register(SdkEvent.SdkReady, TriggerSdkReady); + _eventsManager.NotifyInternalEvent(SdkInternalEvent.SdkReady, null); + + // Act + SdkUpdateFlag = false; + _segmentCache.Update(new List { segmentToAdd, segmentToRemove }, new List { segmentToRemove.Name }, till); + SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + + // Assert + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.SegmentsUpdate, eMetadata.GetEventType()); + + // Act + SdkUpdateFlag = false; + _segmentCache.Update(new List(), new List(), 12345); + + // Assert + Assert.IsFalse(SdkUpdateFlag); + } + + private void sdkUpdate_callback(object sender, EventMetadata metadata) + { + SdkUpdateFlag = true; + eMetadata = metadata; + } + + private void TriggerSdkReady(EventMetadata metaData) + { + SdkReady?.Invoke(this, metaData); + } + + private void TriggerSdkUpdate(EventMetadata metaData) + { + SdkUpdate?.Invoke(this, metaData); + } } } diff --git a/tests/Splitio-tests/Unit Tests/Cache/InMemory/SegmentCacheAsyncTests.cs b/tests/Splitio-tests/Unit Tests/Cache/InMemory/SegmentCacheAsyncTests.cs index c08a7515c..bced73eed 100644 --- a/tests/Splitio-tests/Unit Tests/Cache/InMemory/SegmentCacheAsyncTests.cs +++ b/tests/Splitio-tests/Unit Tests/Cache/InMemory/SegmentCacheAsyncTests.cs @@ -2,8 +2,13 @@ using Splitio.Domain; using Splitio.Services.Cache.Classes; using Splitio.Services.Cache.Interfaces; +using Splitio.Services.Common; +using Splitio.Services.Shared.Classes; +using Splitio.Services.Tasks; +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Splitio_Tests.Unit_Tests.Cache @@ -12,12 +17,20 @@ namespace Splitio_Tests.Unit_Tests.Cache public class SegmentCacheAsyncTests { private readonly ISegmentCache _cache; + private EventsManager _eventsManager; + private bool SdkUpdateFlag = false; + private EventMetadata eMetadata = null; + private InternalEventsTask _internalEventsTask; + public event EventHandler SdkUpdate; + public event EventHandler SdkReady; public SegmentCacheAsyncTests() { var segments = new ConcurrentDictionary(); - - _cache = new InMemorySegmentCache(segments); + _eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + _internalEventsTask = new InternalEventsTask(_eventsManager, new SplitQueue()); + _internalEventsTask.Start(); + _cache = new InMemorySegmentCache(segments, _internalEventsTask); } [TestMethod] @@ -47,5 +60,42 @@ public async Task IsInSegmentAsyncTestTrue() //Assert Assert.IsTrue(result); } + + [TestMethod] + public void NotifyEventsTest() + { + //Arrange + var segmentName = "segment_test"; + var toNotify = new List { { segmentName } }; + SdkUpdate += sdkUpdate_callback; + _eventsManager.Register(SdkEvent.SdkUpdate, TriggerSdkUpdate); + _eventsManager.Register(SdkEvent.SdkReady, TriggerSdkReady); + _eventsManager.NotifyInternalEvent(SdkInternalEvent.SdkReady, null); + + //Act + SdkUpdateFlag = false; + _cache.AddToSegment(segmentName, new List { "abcd", "zzzzf" }); + SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + + //Assert + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.SegmentsUpdate, eMetadata.GetEventType()); + } + + private void sdkUpdate_callback(object sender, EventMetadata metadata) + { + SdkUpdateFlag = true; + eMetadata = metadata; + } + + private void TriggerSdkReady(EventMetadata metaData) + { + SdkReady?.Invoke(this, metaData); + } + + private void TriggerSdkUpdate(EventMetadata metaData) + { + SdkUpdate?.Invoke(this, metaData); + } } } diff --git a/tests/Splitio-tests/Unit Tests/Cache/InMemory/SegmentCacheTests.cs b/tests/Splitio-tests/Unit Tests/Cache/InMemory/SegmentCacheTests.cs index 287f18750..c29771219 100644 --- a/tests/Splitio-tests/Unit Tests/Cache/InMemory/SegmentCacheTests.cs +++ b/tests/Splitio-tests/Unit Tests/Cache/InMemory/SegmentCacheTests.cs @@ -1,19 +1,31 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using Splitio.Domain; using Splitio.Services.Cache.Classes; +using Splitio.Services.Common; +using Splitio.Services.Shared.Classes; +using Splitio.Services.Tasks; +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading; namespace Splitio_Tests.Unit_Tests.Cache { [TestClass] public class SegmentCacheTests { + private bool SdkUpdateFlag = false; + private EventMetadata eMetadata = null; + public event EventHandler SdkUpdate; + public event EventHandler SdkReady; + [TestMethod] public void RegisterSegmentTest() { //Arrange - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); var keys = new List { "abcd", "1234" }; var segmentName = "test"; @@ -29,7 +41,8 @@ public void RegisterSegmentTest() public void IsNotInSegmentTest() { //Arrange - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); var keys = new List { "1234" }; var segmentName = "test"; @@ -45,7 +58,8 @@ public void IsNotInSegmentTest() public void IsInSegmentWithInexistentSegmentTest() { //Arrange - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); //Act var result = segmentCache.IsInSegment("test", "abcd"); @@ -58,7 +72,8 @@ public void IsInSegmentWithInexistentSegmentTest() public void RemoveKeyFromSegmentTest() { //Arrange - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); var keys = new List { "1234" }; var segmentName = "test"; @@ -77,7 +92,8 @@ public void RemoveKeyFromSegmentTest() public void SetAndGetChangeNumberTest() { //Arrange - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); var segmentName = "test"; //Act @@ -88,5 +104,57 @@ public void SetAndGetChangeNumberTest() //Assert Assert.AreEqual(1234, result); } + + [TestMethod] + public void NotifyEventsTest() + { + //Arrange + var eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + internalEventsTask.Start(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask); + var keys = new List { "1234" }; + var segmentName = "test"; + var toNotify = new List { { segmentName } }; + SdkUpdate += sdkUpdate_callback; + eventsManager.Register(SdkEvent.SdkUpdate, TriggerSdkUpdate); + eventsManager.Register(SdkEvent.SdkReady, TriggerSdkReady); + eventsManager.NotifyInternalEvent(SdkInternalEvent.SdkReady, null); + + // Act + SdkUpdateFlag = false; + segmentCache.AddToSegment(segmentName, keys); + SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(2000)); + + //Assert + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.SegmentsUpdate, eMetadata.GetEventType()); + + // Act + SdkUpdateFlag = false; + eMetadata = null; + segmentCache.RemoveFromSegment(segmentName, keys); + SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(2000)); + + //Assert + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.SegmentsUpdate, eMetadata.GetEventType()); + } + + private void sdkUpdate_callback(object sender, EventMetadata metadata) + { + SdkUpdateFlag = true; + eMetadata = metadata; + } + + private void TriggerSdkReady(EventMetadata metaData) + { + SdkReady?.Invoke(this, metaData); + } + + private void TriggerSdkUpdate(EventMetadata metaData) + { + SdkUpdate?.Invoke(this, metaData); + } } } diff --git a/tests/Splitio-tests/Unit Tests/Cache/InMemory/SplitCacheAsyncTests.cs b/tests/Splitio-tests/Unit Tests/Cache/InMemory/SplitCacheAsyncTests.cs index 3c4541e97..a30114b2b 100644 --- a/tests/Splitio-tests/Unit Tests/Cache/InMemory/SplitCacheAsyncTests.cs +++ b/tests/Splitio-tests/Unit Tests/Cache/InMemory/SplitCacheAsyncTests.cs @@ -2,10 +2,14 @@ using Splitio.Domain; using Splitio.Services.Cache.Classes; using Splitio.Services.Cache.Interfaces; +using Splitio.Services.Common; using Splitio.Services.Filters; +using Splitio.Services.Tasks; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace Splitio_Tests.Unit_Tests.Cache @@ -15,13 +19,21 @@ public class SplitCacheAsyncTests { private readonly IFlagSetsFilter _flagSetsFilter; private readonly IFeatureFlagCache _cache; + private readonly EventsManager _eventsManager; + private readonly InternalEventsTask _internalEventsTask; + private bool SdkUpdateFlag = false; + private EventMetadata eMetadata = null; + public event EventHandler SdkUpdate; + public event EventHandler SdkReady; public SplitCacheAsyncTests() { _flagSetsFilter = new FlagSetsFilter(new HashSet()); var splits = new ConcurrentDictionary(); - - _cache = new InMemorySplitCache(splits, _flagSetsFilter); + _eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + _internalEventsTask = new InternalEventsTask(_eventsManager, new Splitio.Services.Shared.Classes.SplitQueue()); + _internalEventsTask.Start(); + _cache = new InMemorySplitCache(splits, _flagSetsFilter, _internalEventsTask); } [TestMethod] @@ -169,5 +181,70 @@ public async Task GetSplitNamesAsyncReturnsItems() // Assert. Assert.AreEqual(5, result.Count); } + + [TestMethod] + public async Task NotifyUpdateEventTest() + { + // Arrange. + var toAdd = new List(); + var toNotify = new List(); + for (int i = 1; i <= 5; i++) + { + toAdd.Add(new ParsedSplit + { + name = $"feature-flag-{i}", + defaultTreatment = "on", + changeNumber = i + }); + toNotify.Add($"feature-flag-{i}"); + } + SdkUpdate += sdkUpdate_callback; + _eventsManager.Register(SdkEvent.SdkUpdate, TriggerSdkUpdate); + _eventsManager.Register(SdkEvent.SdkReady, TriggerSdkReady); + _eventsManager.NotifyInternalEvent(SdkInternalEvent.SdkReady, null); + + SdkUpdateFlag = false; + _cache.Update(toAdd, new List(), -1); + SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + + // Act. + var result = await _cache.GetSplitNamesAsync(); + + // Assert. + Assert.AreEqual(5, result.Count); + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.FlagsUpdate, eMetadata.GetEventType()); + Assert.IsTrue(eMetadata.GetNames().Count == 5); + for (int i = 1; i <= 5; i++) + { + Assert.IsTrue(eMetadata.GetNames().Contains($"feature-flag-{i}")); + } + + SdkUpdateFlag = false; + eMetadata = null; + _cache.Kill(123, "feature-flag-1", "off"); + SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.FlagsUpdate, eMetadata.GetEventType()); + Assert.IsTrue(eMetadata.GetNames().Count == 1); + Assert.IsTrue(eMetadata.GetNames().Contains($"feature-flag-1")); + } + + private void sdkUpdate_callback(object sender, EventMetadata metadata) + { + SdkUpdateFlag = true; + eMetadata = metadata; + } + + private void TriggerSdkReady(EventMetadata metaData) + { + SdkReady?.Invoke(this, metaData); + } + + private void TriggerSdkUpdate(EventMetadata metaData) + { + SdkUpdate?.Invoke(this, metaData); + } } } diff --git a/tests/Splitio-tests/Unit Tests/Cache/InMemory/SplitCacheTests.cs b/tests/Splitio-tests/Unit Tests/Cache/InMemory/SplitCacheTests.cs index 6d0f24118..cb8450552 100644 --- a/tests/Splitio-tests/Unit Tests/Cache/InMemory/SplitCacheTests.cs +++ b/tests/Splitio-tests/Unit Tests/Cache/InMemory/SplitCacheTests.cs @@ -2,10 +2,14 @@ using Moq; using Splitio.Domain; using Splitio.Services.Cache.Classes; +using Splitio.Services.Common; using Splitio.Services.Filters; +using Splitio.Services.Tasks; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace Splitio_Tests.Unit_Tests.Cache { @@ -13,6 +17,10 @@ namespace Splitio_Tests.Unit_Tests.Cache public class SplitCacheTests { private readonly Mock _flagSetsFilter; + private bool SdkUpdateFlag = false; + private EventMetadata eMetadata = null; + public event EventHandler SdkUpdate; + public event EventHandler SdkReady; public SplitCacheTests() { @@ -23,7 +31,8 @@ public SplitCacheTests() public void AddAndGetSplitTest() { //Arrange - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object, internalEventsTask.Object); var splitName = "test1"; //Act @@ -38,7 +47,8 @@ public void AddAndGetSplitTest() public void AddDuplicateSplitTest() { //Arrange - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object, internalEventsTask.Object); var splitName = "test1"; //Act @@ -58,7 +68,8 @@ public void AddDuplicateSplitTest() public void GetInexistentSplitTest() { //Arrange - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object, internalEventsTask.Object); var splitName = "test1"; //Act @@ -75,7 +86,8 @@ public void RemoveSplitTest() var splitName = "test1"; var splits = new ConcurrentDictionary(); splits.TryAdd(splitName, new ParsedSplit() { name = splitName }); - var splitCache = new InMemorySplitCache(splits, _flagSetsFilter.Object); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(splits, _flagSetsFilter.Object, internalEventsTask.Object); //Act splitCache.Update(new List(), new List { splitName }, -1); @@ -89,7 +101,8 @@ public void RemoveSplitTest() public void SetAndGetChangeNumberTest() { //Arrange - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object, internalEventsTask.Object); var changeNumber = 1234; //Act @@ -104,7 +117,8 @@ public void SetAndGetChangeNumberTest() public void GetAllSplitsTest() { //Arrange - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object, internalEventsTask.Object); var splitName = "test1"; var splitName2 = "test2"; @@ -123,7 +137,8 @@ public void GetAllSplitsTest() public void AddOrUpdate_WhenUpdateTraffictType_ReturnsTrue() { // Arrange - var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object, internalEventsTask.Object); var splitName = "split_1"; var splitName2 = "split_2"; @@ -165,7 +180,8 @@ public void GetNamesByFlagSetsWithoutFilter() Sets = new HashSet { "set1", "set2" } }); - var splitCache = new InMemorySplitCache(featureFlags, new FlagSetsFilter(new HashSet())); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(featureFlags, new FlagSetsFilter(new HashSet()), internalEventsTask.Object); var flagSetNames = new List { "set1", "set2", "set3", "set4" }; // Act. @@ -208,8 +224,8 @@ public void GetNamesByFlagSetsWithFilters() defaultTreatment = "on", Sets = new HashSet { "set1", "set2" } }); - - var splitCache = new InMemorySplitCache(featureFlags, new FlagSetsFilter(new HashSet() { "set1", "set2" })); + Mock internalEventsTask = new Mock(); + var splitCache = new InMemorySplitCache(featureFlags, new FlagSetsFilter(new HashSet() { "set1", "set2" }), internalEventsTask.Object); var flagSetNames = new List { "set1", "set2", "set3", "set4" }; // Act. @@ -226,5 +242,68 @@ public void GetNamesByFlagSetsWithFilters() var set4 = result["set4"]; Assert.IsFalse(set4.Any()); } + + [TestMethod] + public void NotifyUpdateEventTest() + { + // Arrange. + var eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new Splitio.Services.Shared.Classes.SplitQueue()); + internalEventsTask.Start(); + var splitCache = new InMemorySplitCache(new ConcurrentDictionary(), _flagSetsFilter.Object, internalEventsTask); + var splitName = "test1"; + + var toNotify = new List { { splitName } }; + SdkUpdate += sdkUpdate_callback; + eventsManager.Register(SdkEvent.SdkUpdate, TriggerSdkUpdate); + eventsManager.Register(SdkEvent.SdkReady, TriggerSdkReady); + eventsManager.NotifyInternalEvent(SdkInternalEvent.SdkReady, null); + + // Act. + SdkUpdateFlag = false; + splitCache.Update(new List { new ParsedSplit() { name = splitName } }, new List(), -1); + SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + + // Assert. + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.FlagsUpdate, eMetadata.GetEventType()); + Assert.IsTrue(eMetadata.GetNames().Count == 1); + Assert.IsTrue(eMetadata.GetNames().Contains(splitName)); + + // Act. + SdkUpdateFlag = false; + eMetadata = null; + splitCache.Kill(123, splitName, "off"); + SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + + // Assert. + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.FlagsUpdate, eMetadata.GetEventType()); + Assert.IsTrue(eMetadata.GetNames().Count == 1); + Assert.IsTrue(eMetadata.GetNames().Contains(splitName)); + + // Act. + SdkUpdateFlag = false; + splitCache.Update(new List(), new List(), 1234); + + // Assert. + Assert.IsFalse(SdkUpdateFlag); + } + + private void sdkUpdate_callback(object sender, EventMetadata metadata) + { + SdkUpdateFlag = true; + eMetadata = metadata; + } + + private void TriggerSdkReady(EventMetadata metaData) + { + SdkReady?.Invoke(this, metaData); + } + + private void TriggerSdkUpdate(EventMetadata metaData) + { + SdkUpdate?.Invoke(this, metaData); + } } } diff --git a/tests/Splitio-tests/Unit Tests/Client/LocalhostClientForTesting.cs b/tests/Splitio-tests/Unit Tests/Client/LocalhostClientForTesting.cs index 09c6b39ea..49a16aeea 100644 --- a/tests/Splitio-tests/Unit Tests/Client/LocalhostClientForTesting.cs +++ b/tests/Splitio-tests/Unit Tests/Client/LocalhostClientForTesting.cs @@ -1,11 +1,10 @@ using Splitio.Services.Client.Classes; -using Splitio.Services.Impressions.Classes; namespace Splitio_Tests.Unit_Tests.Client { public class LocalhostClientForTesting : LocalhostClient { - public LocalhostClientForTesting(string filePath, FallbackTreatmentCalculator fallbackTreatmentCalculator) : base(new ConfigurationOptions { LocalhostFilePath = filePath }, fallbackTreatmentCalculator) + public LocalhostClientForTesting(string filePath) : base(new ConfigurationOptions { LocalhostFilePath = filePath }) { } } } diff --git a/tests/Splitio-tests/Unit Tests/Client/LocalhostClientUnitTests.cs b/tests/Splitio-tests/Unit Tests/Client/LocalhostClientUnitTests.cs index aaf4a6a2c..66a76331b 100644 --- a/tests/Splitio-tests/Unit Tests/Client/LocalhostClientUnitTests.cs +++ b/tests/Splitio-tests/Unit Tests/Client/LocalhostClientUnitTests.cs @@ -1,22 +1,19 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Splitio.Domain; using Splitio.Services.Client.Classes; -using Splitio.Services.Impressions.Classes; using Splitio.Services.Shared.Classes; namespace Splitio_Tests.Unit_Tests.Client { [TestClass] public class LocalhostClientUnitTests - { + { private readonly string rootFilePath; - private readonly FallbackTreatmentCalculator _fallbackTreatmentCalculator; public LocalhostClientUnitTests() { // This line is to clean the warnings. rootFilePath = string.Empty; - _fallbackTreatmentCalculator = new FallbackTreatmentCalculator(new FallbackTreatmentsConfiguration()); #if NET_LATEST rootFilePath = @"Resources\"; @@ -28,7 +25,7 @@ public LocalhostClientUnitTests() public void GetTreatmentShouldReturnControlIfSplitNotFound() { //Arrange - var splitClient = new LocalhostClient(new ConfigurationOptions { LocalhostFilePath = $"{rootFilePath}test.splits" }, _fallbackTreatmentCalculator); + var splitClient = new LocalhostClient(new ConfigurationOptions { LocalhostFilePath = $"{rootFilePath}test.splits" }); //Act var result = splitClient.GetTreatment("test", "test"); @@ -41,7 +38,7 @@ public void GetTreatmentShouldReturnControlIfSplitNotFound() [DeploymentItem(@"Resources\test.splits")] public void GetTreatmentShouldRunAsSingleKeyUsingNullBucketingKey() { - var splitClient = new LocalhostClient(new ConfigurationOptions { LocalhostFilePath = $"{rootFilePath}test.splits" }, _fallbackTreatmentCalculator); + var splitClient = new LocalhostClient(new ConfigurationOptions { LocalhostFilePath = $"{rootFilePath}test.splits" }); splitClient.BlockUntilReady(1000); //Act @@ -57,9 +54,9 @@ public void GetTreatmentShouldRunAsSingleKeyUsingNullBucketingKey() public void TrackShouldNotStoreEvents() { //Arrange - var splitClient = new LocalhostClientForTesting($"{rootFilePath}test.splits", _fallbackTreatmentCalculator); + var splitClient = new LocalhostClientForTesting($"{rootFilePath}test.splits"); splitClient.BlockUntilReady(1000); - + //Act var result = splitClient.Track("test", "test", "test"); @@ -73,7 +70,7 @@ public void Destroy() { //Arrange var _factoryInstantiationsService = FactoryInstantiationsService.Instance(); - var splitClient = new LocalhostClientForTesting($"{rootFilePath}test.splits", _fallbackTreatmentCalculator); + var splitClient = new LocalhostClientForTesting($"{rootFilePath}test.splits"); //Act splitClient.BlockUntilReady(10000); diff --git a/tests/Splitio-tests/Unit Tests/Client/SdkReadinessGatesUnitTests.cs b/tests/Splitio-tests/Unit Tests/Client/SdkReadinessGatesUnitTests.cs index bf1e7b1ba..0d4a8f87d 100644 --- a/tests/Splitio-tests/Unit Tests/Client/SdkReadinessGatesUnitTests.cs +++ b/tests/Splitio-tests/Unit Tests/Client/SdkReadinessGatesUnitTests.cs @@ -1,16 +1,20 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using Splitio.Services.Client.Classes; +using Splitio.Services.Tasks; namespace Splitio_Tests.Unit_Tests.Client { [TestClass] public class InMemoryReadinessGatesCacheUnitTests { + [TestMethod] public void IsSDKReadyShouldReturnFalseIfSplitsAreNotReady() { //Arrange - var gates = new InMemoryReadinessGatesCache(); + var internalEventsTask = new Mock(); + var gates = new InMemoryReadinessGatesCache(internalEventsTask.Object); //Act var result = gates.IsReady(); diff --git a/tests/Splitio-tests/Unit Tests/Client/SplitClientAsyncTests.cs b/tests/Splitio-tests/Unit Tests/Client/SplitClientAsyncTests.cs index ebacf3498..5fd49effa 100644 --- a/tests/Splitio-tests/Unit Tests/Client/SplitClientAsyncTests.cs +++ b/tests/Splitio-tests/Unit Tests/Client/SplitClientAsyncTests.cs @@ -2,7 +2,6 @@ using Moq; using Splitio.Domain; using Splitio.Services.Cache.Interfaces; -using Splitio.Services.Client.Interfaces; using Splitio.Services.Common; using Splitio.Services.EngineEvaluator; using Splitio.Services.Evaluator; @@ -28,6 +27,7 @@ public class SplitClientAsyncTests private readonly Mock _impressionsManager; private readonly Mock _syncManager; private Mock _telemetryEvaluationProducer; + private readonly Mock _fallbackTreatmentCalculation; private SplitClientForTesting _splitClient; public SplitClientAsyncTests() { @@ -39,8 +39,8 @@ public SplitClientAsyncTests() _impressionsManager = new Mock(); _syncManager = new Mock(); _telemetryEvaluationProducer = new Mock(); - - _splitClient = new SplitClientForTesting(_splitCache.Object, _eventsLog.Object, _impressionsLog.Object, _blockUntilReadyService.Object, _evaluator.Object, _impressionsManager.Object, _syncManager.Object, new FallbackTreatmentCalculator(new FallbackTreatmentsConfiguration()), _telemetryEvaluationProducer.Object); + _fallbackTreatmentCalculation = new Mock(); + _splitClient = new SplitClientForTesting(_splitCache.Object, _eventsLog.Object, _impressionsLog.Object, _blockUntilReadyService.Object, _evaluator.Object, _impressionsManager.Object, _syncManager.Object, _telemetryEvaluationProducer.Object, _fallbackTreatmentCalculation.Object); } #region GetTreatmentAsync @@ -51,6 +51,9 @@ public async Task GetTreatment_WithNullKey_ReturnsControl() _blockUntilReadyService .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); // Act var result = await _splitClient.GetTreatmentAsync((string)null, string.Empty); @@ -66,6 +69,9 @@ public async Task GetTreatment_WhenNameDoesntExist_ReturnsControl() _blockUntilReadyService .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); _evaluator .Setup(mock => mock.EvaluateFeaturesAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), true)) @@ -89,6 +95,10 @@ public async Task GetTreatment_WhenExist_ReturnsOn() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + _evaluator .Setup(mock => mock.EvaluateFeaturesAsync(It.IsAny(), It.IsAny(), new List() { "feature_flag_test" }, It.IsAny>(), true)) .ReturnsAsync(new List { new TreatmentResult("feature_flag_test", Labels.DefaultRule, "on", false, 1000, null) }); @@ -119,6 +129,10 @@ public async Task GetTreatments_WithEmptyKey_ShouldReturnControl() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var results = await _splitClient.GetTreatmentsAsync(string.Empty, new List { string.Empty }); @@ -248,6 +262,10 @@ public async Task GetTreatmentWithConfig_WithEmptyKey_ShouldReturnControl() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var result = await _splitClient.GetTreatmentWithConfigAsync(string.Empty, string.Empty); @@ -586,6 +604,10 @@ public async Task GetTreatmentsWithConfig_WithEmptyKey_ShouldReturnControl() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var results = await _splitClient.GetTreatmentsWithConfigAsync(string.Empty, new List { string.Empty }); @@ -1005,7 +1027,8 @@ public async Task FallbackTreatments_WhenFlagDoesNotExist() FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global", "\"prop\":\"global\""), new Dictionary() { { splitName, new FallbackTreatment("off-local", "\"prop\":\"local\"") } }); FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); IEvaluator _evaluator = new Splitio.Services.Evaluator.Evaluator(_splitCache.Object, _splitter.Object, _telemetryEvaluationProducer.Object, fallbackTreatmentCalculator); - _splitClient = new SplitClientForTesting(_splitCache.Object, _eventsLog.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluator, _impressionsManager.Object, _syncManager.Object, fallbackTreatmentCalculator, _telemetryEvaluationProducer.Object); + _splitClient = new SplitClientForTesting(_splitCache.Object, _eventsLog.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluator, _impressionsManager.Object, _syncManager.Object, _telemetryEvaluationProducer.Object, + fallbackTreatmentCalculator); _splitClient.BlockUntilReady(1000); string treatment = await _splitClient.GetTreatmentAsync("key", splitName); @@ -1077,13 +1100,13 @@ public async Task FallbackTreatments_WhenExceptionInClient() .Returns(true); FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global", "\"prop\":\"global\""), new Dictionary() { { splitName, new FallbackTreatment("off-local", "\"prop\":\"local\"") } }); - FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); _evaluator = new Mock(); _evaluator .Setup(mock => mock.EvaluateFeaturesAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), true)) .Throws(); - _splitClient = new SplitClientForTesting(_splitCache.Object, _eventsLog.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluator.Object, _impressionsManager.Object, _syncManager.Object, fallbackTreatmentCalculator, _telemetryEvaluationProducer.Object); + _splitClient = new SplitClientForTesting(_splitCache.Object, _eventsLog.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluator.Object, _impressionsManager.Object, _syncManager.Object, _telemetryEvaluationProducer.Object, + new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration)); _splitClient.BlockUntilReady(1000); string treatment = await _splitClient.GetTreatmentAsync("key", splitName); diff --git a/tests/Splitio-tests/Unit Tests/Client/SplitClientForTesting.cs b/tests/Splitio-tests/Unit Tests/Client/SplitClientForTesting.cs index 80587cd2c..fe11a14ed 100644 --- a/tests/Splitio-tests/Unit Tests/Client/SplitClientForTesting.cs +++ b/tests/Splitio-tests/Unit Tests/Client/SplitClientForTesting.cs @@ -20,10 +20,11 @@ public SplitClientForTesting(IFeatureFlagCacheConsumer featureFlagCacheConsumer, IEvaluator evaluator, IImpressionsManager impressionsManager, ISyncManager syncManager, - FallbackTreatmentCalculator fallbackTreatmentCalculator, - ITelemetryEvaluationProducer telemetryEvaluationProducer) - : base("SplitClientForTesting", fallbackTreatmentCalculator) + ITelemetryEvaluationProducer telemetryEvaluationProducer, + IFallbackTreatmentCalculator fallbackTreatmentCalculator) + : base("SplitClientForTesting") { + _fallbackTreatmentCalculator = fallbackTreatmentCalculator; _eventsLog = eventsLog; _impressionsLog = impressionsLog; _blockUntilReadyService = blockUntilReadyService; diff --git a/tests/Splitio-tests/Unit Tests/Client/SplitClientUnitTests.cs b/tests/Splitio-tests/Unit Tests/Client/SplitClientUnitTests.cs index 842b81a7d..baa0f2df1 100644 --- a/tests/Splitio-tests/Unit Tests/Client/SplitClientUnitTests.cs +++ b/tests/Splitio-tests/Unit Tests/Client/SplitClientUnitTests.cs @@ -1,13 +1,7 @@ -using AnyOfTypes; -using HandlebarsDotNet.Features; -using Microsoft.CSharp.RuntimeBinder; -using Microsoft.OpenApi.Any; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Splitio.Domain; using Splitio.Services.Cache.Interfaces; -using Splitio.Services.Client.Classes; -using Splitio.Services.Client.Interfaces; using Splitio.Services.Common; using Splitio.Services.EngineEvaluator; using Splitio.Services.Evaluator; @@ -32,6 +26,7 @@ public class SplitClientUnitTests private Mock _impressionsManager; private Mock _syncManager; private Mock _telemetryEvaluationProducer; + private Mock _fallbackTreatmentCalculation; private SplitClientForTesting _splitClientForTesting; [TestInitialize] @@ -44,8 +39,9 @@ public void TestInitialize() _impressionsManager = new Mock(); _syncManager = new Mock(); _telemetryEvaluationProducer = new Mock(); + _fallbackTreatmentCalculation = new Mock(); - _splitClientForTesting = new SplitClientForTesting(_splitCacheMock.Object, _eventsLogMock.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluatorMock.Object, _impressionsManager.Object, _syncManager.Object, new FallbackTreatmentCalculator(new FallbackTreatmentsConfiguration()), _telemetryEvaluationProducer.Object); + _splitClientForTesting = new SplitClientForTesting(_splitCacheMock.Object, _eventsLogMock.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluatorMock.Object, _impressionsManager.Object, _syncManager.Object, _telemetryEvaluationProducer.Object, _fallbackTreatmentCalculation.Object); _splitClientForTesting.BlockUntilReady(1000); } @@ -59,6 +55,10 @@ public void GetTreatment_ShouldReturnControl_WithNullKey() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var result = _splitClientForTesting.GetTreatment((string)null, string.Empty); @@ -74,6 +74,10 @@ public void GetTreatment_ShouldReturnControl_WithNullMatchingKey() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var result = _splitClientForTesting.GetTreatment(new Key(null, string.Empty), string.Empty); @@ -89,6 +93,10 @@ public void GetTreatment_ShouldReturnControl_WithNullMatchingAndBucketingKey() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var result = _splitClientForTesting.GetTreatment(new Key(null, null), string.Empty); @@ -293,6 +301,10 @@ public void GetTreatmentWithConfig_WithEmptyKey_ShouldReturnControl() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var result = _splitClientForTesting.GetTreatmentWithConfig(string.Empty, string.Empty); @@ -309,6 +321,10 @@ public void GetTreatmentWithConfig_WithNullKey_ShouldReturnControl() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var result = _splitClientForTesting.GetTreatmentWithConfig((Key)null, string.Empty); @@ -618,6 +634,10 @@ public void GetTreatmentsWithConfig_WithEmptyKey_ShouldReturnControl() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var results = _splitClientForTesting.GetTreatmentsWithConfig(string.Empty, new List { string.Empty }); @@ -637,6 +657,10 @@ public void GetTreatmentsWithConfig_WithNullKey_ShouldReturnControl() .Setup(mock => mock.IsSdkReady()) .Returns(true); + _fallbackTreatmentCalculation + .Setup(mock => mock.resolve(It.IsAny(), It.IsAny())) + .Returns(new FallbackTreatment("control")); + // Act var results = _splitClientForTesting.GetTreatmentsWithConfig((Key)null, new List { string.Empty }); @@ -779,7 +803,8 @@ public void FallbackTreatments_WhenFlagDoesNotExist() FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global", "\"prop\":\"global\""), new Dictionary() { { splitName, new FallbackTreatment("off-local", "\"prop\":\"local\"") } }); FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); IEvaluator _evaluator = new Splitio.Services.Evaluator.Evaluator(_splitCache.Object, _splitter.Object, _telemetryEvaluationProducer.Object, fallbackTreatmentCalculator); - _splitClientForTesting = new SplitClientForTesting(_splitCache.Object, _eventsLogMock.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluator, _impressionsManager.Object, _syncManager.Object, fallbackTreatmentCalculator, _telemetryEvaluationProducer.Object); + _splitClientForTesting = new SplitClientForTesting(_splitCache.Object, _eventsLogMock.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluator, _impressionsManager.Object, _syncManager.Object, _telemetryEvaluationProducer.Object, + fallbackTreatmentCalculator); _splitClientForTesting.BlockUntilReady(1000); string treatment = _splitClientForTesting.GetTreatment("key", splitName); @@ -851,13 +876,13 @@ public void FallbackTreatments_WhenExceptionInClient() .Returns(true); FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global", "\"prop\":\"global\""), new Dictionary() { { splitName, new FallbackTreatment("off-local", "\"prop\":\"local\"") } }); - FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration); _evaluatorMock = new Mock(); _evaluatorMock .Setup(mock => mock.EvaluateFeatures(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), true)) .Throws(); - _splitClientForTesting = new SplitClientForTesting(_splitCache.Object, _eventsLogMock.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluatorMock.Object, _impressionsManager.Object, _syncManager.Object, fallbackTreatmentCalculator, _telemetryEvaluationProducer.Object); + _splitClientForTesting = new SplitClientForTesting(_splitCache.Object, _eventsLogMock.Object, new Mock().Object, _blockUntilReadyService.Object, _evaluatorMock.Object, _impressionsManager.Object, _syncManager.Object, _telemetryEvaluationProducer.Object, + new FallbackTreatmentCalculator(fallbackTreatmentsConfiguration)); _splitClientForTesting.BlockUntilReady(1000); string treatment = _splitClientForTesting.GetTreatment("key", splitName); diff --git a/tests/Splitio-tests/Unit Tests/Common/EventDeliveryTests.cs b/tests/Splitio-tests/Unit Tests/Common/EventDeliveryTests.cs new file mode 100644 index 000000000..1b09e7b81 --- /dev/null +++ b/tests/Splitio-tests/Unit Tests/Common/EventDeliveryTests.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Splitio.Domain; +using Splitio.Services.Common; +using System; +using System.Threading; + +namespace Splitio_Tests.Unit_Tests.Common +{ + [TestClass] + public class EventDeliveryTests + { + private bool SdkReady = false; + private EventMetadata eMetadata = null; + + [TestMethod] + public void TestFiringEvents() + { + //Act + EventDelivery eventDelivery = new EventDelivery(); + + eventDelivery.Deliver(SdkEvent.SdkReady, null, SdkReadyCallback); + SpinWait.SpinUntil(() => SdkReady, TimeSpan.FromMilliseconds(1000)); + Assert.IsTrue(SdkReady); + + eventDelivery.Deliver(SdkEvent.SdkReady, null, SdkReadyCallbackWithException); + // should not cause exception here + } + + private void SdkReadyCallback(EventMetadata metadata) + { + SdkReady = true; + eMetadata = metadata; + } + + private void SdkReadyCallbackWithException(EventMetadata metadata) + { + throw new Exception("something wrong"); + } + } +} diff --git a/tests/Splitio-tests/Unit Tests/Common/EventMetadataTests.cs b/tests/Splitio-tests/Unit Tests/Common/EventMetadataTests.cs new file mode 100644 index 000000000..d94cf0bbd --- /dev/null +++ b/tests/Splitio-tests/Unit Tests/Common/EventMetadataTests.cs @@ -0,0 +1,23 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using Splitio.Domain; + +namespace Splitio_Tests.Unit_Tests.Common +{ + [TestClass] + public class EventMetadataTests + { + + [TestMethod] + public void InstanceWithInput() + { + //Act + var result = new EventMetadata(SdkEventType.FlagsUpdate, new List() { { "feature1" } }); + + //Assert + Assert.AreEqual(1, result.GetNames().Count); + Assert.AreEqual(SdkEventType.FlagsUpdate, result.GetEventType()); + Assert.IsTrue(result.GetNames().Contains("feature1")); + } + } +} diff --git a/tests/Splitio-tests/Unit Tests/Common/EventsManagerConfigTests.cs b/tests/Splitio-tests/Unit Tests/Common/EventsManagerConfigTests.cs new file mode 100644 index 000000000..3dec9f264 --- /dev/null +++ b/tests/Splitio-tests/Unit Tests/Common/EventsManagerConfigTests.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Splitio.Domain; + +namespace Splitio_Tests.Unit_Tests.Common +{ + [TestClass] + public class EventsManagerConfigTests + { + + [TestMethod] + public void BuildInstance() + { + //Act + EventsManagerConfig config = new EventsManagerConfig(); + + //Assert + config.RequireAll.TryGetValue(SdkEvent.SdkReady, out var require1); + Assert.AreEqual(1, require1.Count); + Assert.IsTrue(require1.Contains(SdkInternalEvent.SdkReady)); + + config.Prerequisites.TryGetValue(SdkEvent.SdkUpdate, out var ready2); + Assert.IsTrue(ready2.Contains(SdkEvent.SdkReady)); + + config.ExecutionLimits.TryGetValue(SdkEvent.SdkUpdate, out var update); + Assert.AreEqual(-1, update); + config.ExecutionLimits.TryGetValue(SdkEvent.SdkReady, out var ready); + Assert.AreEqual(1, ready); + + config.RequireAny.TryGetValue(SdkEvent.SdkUpdate, out var require2); + Assert.AreEqual(4, require2.Count); + Assert.IsTrue(require2.Contains(SdkInternalEvent.SegmentsUpdated)); + Assert.IsTrue(require2.Contains(SdkInternalEvent.RuleBasedSegmentsUpdated)); + Assert.IsTrue(require2.Contains(SdkInternalEvent.FlagKilledNotification)); + Assert.IsTrue(require2.Contains(SdkInternalEvent.FlagsUpdated)); + + int order = 0; + Assert.AreEqual(2, config.EvaluationOrder.Count); + foreach (var sdkEvent in config.EvaluationOrder) + { + order++; + switch (order) + { + case 1: + Assert.AreEqual(SdkEvent.SdkReady, sdkEvent); + break; + case 2: + Assert.AreEqual(SdkEvent.SdkUpdate, sdkEvent); + break; + } + } + } + } +} diff --git a/tests/Splitio-tests/Unit Tests/Common/EventsManagerTests.cs b/tests/Splitio-tests/Unit Tests/Common/EventsManagerTests.cs new file mode 100644 index 000000000..e4f3e25aa --- /dev/null +++ b/tests/Splitio-tests/Unit Tests/Common/EventsManagerTests.cs @@ -0,0 +1,159 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Splitio.Domain; +using Splitio.Services.Common; +using System; +using System.Collections.Generic; + +namespace Splitio_Tests.Unit_Tests.Common +{ + [TestClass] + public class EventsManagerTests + { + private bool SdkReadyFlag = false; + private bool SdkReadyFlag2 = false; + private bool SdkUpdateFlag = false; + private string FireFirst = ""; + private EventMetadata eMetadata = null; + public event EventHandler SdkReady; + public event EventHandler SdkUpdate; + + [TestMethod] + public void TestFiringEvents() + { + //Act + EventsManagerConfig config = new EventsManagerConfig(); + EventsManager eventsManager = new EventsManager(config, new EventDelivery()); + + SdkReady += sdkReady_callback; + SdkReady += sdkReady_callback2; + SdkUpdate += sdkUpdate_callback; + Dictionary metaData = new Dictionary + { + { "flags", new List {{ "flag1" }} } + }; + + eventsManager.Register(SdkEvent.SdkReady, TriggerSdkReady); + eventsManager.Register(SdkEvent.SdkUpdate, TriggerSdkUpdate); + + eventsManager.NotifyInternalEvent(SdkInternalEvent.RuleBasedSegmentsUpdated, new EventMetadata(SdkEventType.SegmentsUpdate, new List())); + eventsManager.NotifyInternalEvent(SdkInternalEvent.FlagKilledNotification, new EventMetadata(SdkEventType.FlagsUpdate, new List { { "flag1" } })); + eventsManager.NotifyInternalEvent(SdkInternalEvent.SegmentsUpdated, new EventMetadata(SdkEventType.SegmentsUpdate, new List())); + eventsManager.NotifyInternalEvent(SdkInternalEvent.FlagsUpdated, new EventMetadata(SdkEventType.FlagsUpdate, new List { { "flag1" } })); + Assert.IsFalse(SdkReadyFlag); + Assert.IsFalse(SdkUpdateFlag); + + ResetAllVariables(); + eventsManager.NotifyInternalEvent(SdkInternalEvent.SdkReady, null); + System.Threading.SpinWait.SpinUntil(() => SdkReadyFlag, TimeSpan.FromMilliseconds(1000)); + System.Threading.SpinWait.SpinUntil(() => SdkReadyFlag2, TimeSpan.FromMilliseconds(1000)); + Assert.IsTrue(SdkReadyFlag); + Assert.IsTrue(SdkReadyFlag2); + Assert.IsFalse(SdkUpdateFlag); + + ResetAllVariables(); + eventsManager.NotifyInternalEvent(SdkInternalEvent.FlagKilledNotification, new EventMetadata(SdkEventType.FlagsUpdate, new List { { "flag1" } })); + System.Threading.SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + Assert.IsFalse(SdkReadyFlag); + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.FlagsUpdate, eMetadata.GetEventType()); + VerifyMetadata(eMetadata); + + ResetAllVariables(); + eventsManager.NotifyInternalEvent(SdkInternalEvent.SegmentsUpdated, new EventMetadata(SdkEventType.SegmentsUpdate, new List())); + System.Threading.SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + Assert.IsFalse(SdkReadyFlag); + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.SegmentsUpdate, eMetadata.GetEventType()); + + ResetAllVariables(); + eventsManager.NotifyInternalEvent(SdkInternalEvent.RuleBasedSegmentsUpdated, new EventMetadata(SdkEventType.SegmentsUpdate, new List())); + System.Threading.SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + Assert.IsFalse(SdkReadyFlag); + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.SegmentsUpdate, eMetadata.GetEventType()); + + ResetAllVariables(); + eventsManager.NotifyInternalEvent(SdkInternalEvent.FlagsUpdated, new EventMetadata(SdkEventType.FlagsUpdate, new List { { "flag1" } })); + System.Threading.SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(1000)); + Assert.IsFalse(SdkReadyFlag); + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual(SdkEventType.FlagsUpdate, eMetadata.GetEventType()); + VerifyMetadata(eMetadata); + + eventsManager.Unregister(SdkEvent.SdkUpdate); + eventsManager.Unregister(SdkEvent.SdkUpdate); // should not cause exception + ResetAllVariables(); + eventsManager.NotifyInternalEvent(SdkInternalEvent.FlagsUpdated, new EventMetadata(SdkEventType.FlagsUpdate, new List { { "flag1" } })); + Assert.IsFalse(SdkReadyFlag); + Assert.IsFalse(SdkUpdateFlag); + } + + [TestMethod] + public void TestFireOrderEvents() + { + //Act + EventsManagerConfig config = new EventsManagerConfig(); + EventsManager eventsManager = new EventsManager(config, new EventDelivery()); + + SdkReady += sdkReady_callback; + SdkUpdate += sdkUpdate_callback; + + eventsManager.Register(SdkEvent.SdkReady, TriggerSdkReady); + eventsManager.Register(SdkEvent.SdkUpdate, TriggerSdkUpdate); + + ResetAllVariables(); + eventsManager.NotifyInternalEvent(SdkInternalEvent.SdkReady, null); + eventsManager.NotifyInternalEvent(SdkInternalEvent.FlagsUpdated, null); + System.Threading.SpinWait.SpinUntil(() => SdkUpdateFlag, TimeSpan.FromMilliseconds(2000)); + System.Threading.SpinWait.SpinUntil(() => SdkReadyFlag, TimeSpan.FromMilliseconds(2000)); + Assert.IsTrue(SdkReadyFlag); + Assert.IsTrue(SdkUpdateFlag); + Assert.AreEqual("SdkReady", FireFirst); + } + + void ResetAllVariables() + { + SdkReadyFlag = false; + SdkReadyFlag2 = false; + eMetadata = null; + SdkUpdateFlag = false; + FireFirst = ""; + } + + static void VerifyMetadata(EventMetadata eMetdata) + { + Assert.IsTrue(eMetdata.GetNames().Count == 1); + Assert.IsTrue(eMetdata.GetNames().Contains("flag1")); + } + + private void sdkUpdate_callback(object sender, EventMetadata metadata) + { + SdkUpdateFlag = true; + eMetadata = metadata; + if (FireFirst.Equals("")) FireFirst = "SdkUpdate"; + } + + private void sdkReady_callback(object sender, EventMetadata metadata) + { + SdkReadyFlag = true; + eMetadata = metadata; + if (FireFirst.Equals("")) FireFirst = "SdkReady"; + } + + private void sdkReady_callback2(object sender, EventMetadata metadata) + { + SdkReadyFlag2 = true; + eMetadata = metadata; + } + + private void TriggerSdkReady(EventMetadata metaData) + { + SdkReady?.Invoke(this, metaData); + } + + private void TriggerSdkUpdate(EventMetadata metaData) + { + SdkUpdate?.Invoke(this, metaData); + } + } +} diff --git a/tests/Splitio-tests/Unit Tests/Common/SyncManagerTests.cs b/tests/Splitio-tests/Unit Tests/Common/SyncManagerTests.cs index f6a238147..f6787387f 100644 --- a/tests/Splitio-tests/Unit Tests/Common/SyncManagerTests.cs +++ b/tests/Splitio-tests/Unit Tests/Common/SyncManagerTests.cs @@ -24,7 +24,6 @@ public class SyncManagerTests private readonly Mock _statusManager; private readonly Mock _telemetrySyncTask; private readonly Mock _backoff; - private readonly SplitQueue _streamingStatusQueue; public SyncManagerTests() diff --git a/tests/Splitio-tests/Unit Tests/Impressions/ImpressionsCounterTests.cs b/tests/Splitio-tests/Unit Tests/Impressions/ImpressionsCounterTests.cs index 77ce67e35..0aa170f7e 100644 --- a/tests/Splitio-tests/Unit Tests/Impressions/ImpressionsCounterTests.cs +++ b/tests/Splitio-tests/Unit Tests/Impressions/ImpressionsCounterTests.cs @@ -1,8 +1,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Splitio.Domain; using Splitio.Services.Client.Classes; +using Splitio.Services.Common; using Splitio.Services.Impressions.Classes; using Splitio.Services.Impressions.Interfaces; +using Splitio.Services.Shared.Classes; using Splitio.Services.Tasks; using Splitio_Tests.Resources; using System; @@ -27,7 +30,9 @@ public void Start_ShouldSendImpressionsCount() { // Arrange. var config = new ComponentConfig(5, 5); - var statusManager = new InMemoryReadinessGatesCache(); + EventsManager eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + var statusManager = new InMemoryReadinessGatesCache(internalEventsTask); var taskManager = new TasksManager(statusManager); var task = taskManager.NewPeriodicTask(Splitio.Enums.Task.ImpressionsCountSender, 1); var sendBulkDataTask = taskManager.NewOnTimeTask(Splitio.Enums.Task.ImpressionCounterSendBulkData); @@ -51,7 +56,9 @@ public void Start_ShouldNotSendImpressionsCount() { // Arrange. var config = new ComponentConfig(5, 5); - var statusManager = new InMemoryReadinessGatesCache(); + EventsManager eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + var statusManager = new InMemoryReadinessGatesCache(internalEventsTask); var taskManager = new TasksManager(statusManager); var task = taskManager.NewPeriodicTask(Splitio.Enums.Task.ImpressionsCountSender, 1); var sendBulkDataTask = taskManager.NewOnTimeTask(Splitio.Enums.Task.ImpressionCounterSendBulkData); @@ -70,7 +77,9 @@ public async Task Stop_ShouldSendImpressionsCount() { // Arrange. var config = new ComponentConfig(5, 5); - var statusManager = new InMemoryReadinessGatesCache(); + EventsManager eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + var statusManager = new InMemoryReadinessGatesCache(internalEventsTask); var taskManager = new TasksManager(statusManager); var task = taskManager.NewPeriodicTask(Splitio.Enums.Task.ImpressionsCountSender, 100); var sendBulkDataTask = taskManager.NewOnTimeTask(Splitio.Enums.Task.ImpressionCounterSendBulkData); @@ -94,7 +103,9 @@ public async Task Stop_ShouldSendImpressionsCount() public async Task ShouldSend2BulksOfImpressions() { var config = new ComponentConfig(6, 3); - var statusManager = new InMemoryReadinessGatesCache(); + EventsManager eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + var statusManager = new InMemoryReadinessGatesCache(internalEventsTask); var taskManager = new TasksManager(statusManager); var task = taskManager.NewPeriodicTask(Splitio.Enums.Task.ImpressionsCountSender, 100); var sendBulkDataTask = taskManager.NewOnTimeTask(Splitio.Enums.Task.ImpressionCounterSendBulkData); diff --git a/tests/Splitio-tests/Unit Tests/Impressions/RedisImpressionsCacheTests.cs b/tests/Splitio-tests/Unit Tests/Impressions/RedisImpressionsCacheTests.cs index de5abd43e..b1addd5c0 100644 --- a/tests/Splitio-tests/Unit Tests/Impressions/RedisImpressionsCacheTests.cs +++ b/tests/Splitio-tests/Unit Tests/Impressions/RedisImpressionsCacheTests.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Splitio.Domain; using Splitio.Redis.Services.Cache.Classes; using Splitio.Redis.Services.Cache.Interfaces; using Splitio.Redis.Services.Domain; @@ -14,8 +15,8 @@ namespace Splitio_Tests.Unit_Tests.Impressions public class RedisImpressionsCacheTests { private Mock _redisAdapter; - - private IImpressionsCache _cache; + + private RedisImpressionsCache _cache; [TestInitialize] public void Initialization() @@ -91,5 +92,29 @@ await _cache.RecordUniqueKeysAsync(new List _redisAdapter.Verify(mock => mock.ListRightPushAsync(key, expected2), Times.Once); _redisAdapter.Verify(mock => mock.KeyExpireAsync(key, new TimeSpan(0, 0, 3600)), Times.Never); } + + [TestMethod] + public void CorrectFormatStoreImpressions() + { + // Arrange. + var impressions = new List + { + new KeyImpression("matching-key", "feature-1", "treatment", 34534546, 3333444, "label", "bucketing-key", false), + new KeyImpression("matching-key", "feature-1", "treatment", 34534550, 3333444, "label", "bucketing-key", false, 34534546), + new KeyImpression("matching-key", "feature-2", "treatment", 34534546, 3333444, "label", "bucketing-key", false), + }; + impressions[2].properties = "{\"prop\":\"val\"}"; + + // Act. + var result = _cache.GetImpressions(impressions); + var impression1 = "{\"m\":{\"s\":\"version\",\"i\":\"ip\",\"n\":\"mm\"},\"i\":{\"k\":\"matching-key\",\"b\":\"bucketing-key\",\"f\":\"feature-1\",\"t\":\"treatment\",\"r\":\"label\",\"c\":3333444,\"m\":34534546}}"; + var impression2 = "{\"m\":{\"s\":\"version\",\"i\":\"ip\",\"n\":\"mm\"},\"i\":{\"k\":\"matching-key\",\"b\":\"bucketing-key\",\"f\":\"feature-1\",\"t\":\"treatment\",\"r\":\"label\",\"c\":3333444,\"m\":34534550,\"pt\":34534546}}"; + var impression3 = "{\"m\":{\"s\":\"version\",\"i\":\"ip\",\"n\":\"mm\"},\"i\":{\"k\":\"matching-key\",\"b\":\"bucketing-key\",\"f\":\"feature-2\",\"t\":\"treatment\",\"r\":\"label\",\"c\":3333444,\"m\":34534546,\"properties\":\"{\\\"prop\\\":\\\"val\\\"}\"}}"; + + // Assert. + Assert.AreEqual(impression1, result[0].ToString()); + Assert.AreEqual(impression2, result[1].ToString()); + Assert.AreEqual(impression3, result[2].ToString()); + } } } diff --git a/tests/Splitio-tests/Unit Tests/Impressions/UniqueKeysTrackerTests.cs b/tests/Splitio-tests/Unit Tests/Impressions/UniqueKeysTrackerTests.cs index 78a1efc78..01512f512 100644 --- a/tests/Splitio-tests/Unit Tests/Impressions/UniqueKeysTrackerTests.cs +++ b/tests/Splitio-tests/Unit Tests/Impressions/UniqueKeysTrackerTests.cs @@ -6,6 +6,7 @@ using Splitio.Services.Impressions.Interfaces; using Splitio.Services.Tasks; using Splitio.Telemetry.Domain; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; @@ -51,8 +52,7 @@ public async Task PeriodicTask_ShouldSendBulk() // Assert. Assert.IsTrue(_uniqueKeysTracker.Track("key-test", "feature-name-test")); - Thread.Sleep(2000); - + System.Threading.SpinWait.SpinUntil(() => _cache.IsEmpty, TimeSpan.FromMilliseconds(5000)); _senderAdapter.Verify(mock => mock.RecordUniqueKeysAsync(It.IsAny>()), Times.Once); Assert.IsTrue(_uniqueKeysTracker.Track("key-test", "feature-name-test")); @@ -104,7 +104,7 @@ public void Track_WithFullSize_ShouldSendTwoBulk() Thread.Sleep(1000); Assert.IsTrue(_uniqueKeysTracker.Track("key-test-2", "feature-name-test-6")); - Thread.Sleep(3000); + System.Threading.SpinWait.SpinUntil(() => _cache.IsEmpty, TimeSpan.FromMilliseconds(5000)); _senderAdapter.Verify(mock => mock.RecordUniqueKeysAsync(It.IsAny>()), Times.Exactly(2)); _cache.Clear(); @@ -143,8 +143,8 @@ public void Track_WithFullSize_ShouldSplitBulks() Assert.IsTrue(_uniqueKeysTracker2.Track("key-test-4", "feature-name-test-2")); Assert.IsTrue(_uniqueKeysTracker2.Track("key-test-5", "feature-name-test-2")); Assert.IsTrue(_uniqueKeysTracker2.Track("key-test-6", "feature-name-test-2")); - Thread.Sleep(1000); + System.Threading.SpinWait.SpinUntil(() => _cache2.IsEmpty, TimeSpan.FromMilliseconds(5000)); _senderAdapter2.Verify(mock => mock.RecordUniqueKeysAsync(It.IsAny>()), Times.Exactly(4)); _cache2.Clear(); diff --git a/tests/Splitio-tests/Unit Tests/InputValidation/FallbackTreatmentsValidatorTests.cs b/tests/Splitio-tests/Unit Tests/InputValidation/FallbackTreatmentsValidatorTests.cs index 0273d65d9..98042b380 100644 --- a/tests/Splitio-tests/Unit Tests/InputValidation/FallbackTreatmentsValidatorTests.cs +++ b/tests/Splitio-tests/Unit Tests/InputValidation/FallbackTreatmentsValidatorTests.cs @@ -15,30 +15,30 @@ public void Works() FallbackTreatmentsValidator fallbackTreatmentsValidator = new FallbackTreatmentsValidator(); FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("12#2")); - Assert.AreEqual(null, fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration, Splitio.Enums.API.GetTreatment).GlobalFallbackTreatment.Treatment); + Assert.AreEqual(null, fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration).GlobalFallbackTreatment); fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration("12#2"); - Assert.AreEqual(null, fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration, Splitio.Enums.API.GetTreatment).GlobalFallbackTreatment.Treatment); + Assert.AreEqual(null, fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration).GlobalFallbackTreatment); fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( new Dictionary() { { "flag", new FallbackTreatment("12#2") } }); - Assert.AreEqual(0, fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration, Splitio.Enums.API.GetTreatment).ByFlagFallbackTreatment.Count); + Assert.AreEqual(0, fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration).ByFlagFallbackTreatment.Count); fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( new Dictionary() { { "flag", "12#2" } }); - Assert.AreEqual(0, fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration, Splitio.Enums.API.GetTreatment).ByFlagFallbackTreatment.Count); + Assert.AreEqual(0, fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration).ByFlagFallbackTreatment.Count); fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( new FallbackTreatment("on"), new Dictionary() { { "flag", new FallbackTreatment("off") } }); - var processed = fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration, Splitio.Enums.API.GetTreatment); + var processed = fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration); Assert.AreEqual("on", processed.GlobalFallbackTreatment.Treatment); processed.ByFlagFallbackTreatment.TryGetValue("flag", out FallbackTreatment fallbackTreatment); Assert.AreEqual("off", fallbackTreatment.Treatment); fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration("on", new Dictionary() { { "flag", new FallbackTreatment("off") } }); - processed = fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration, Splitio.Enums.API.GetTreatment); + processed = fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration); Assert.AreEqual("on", processed.GlobalFallbackTreatment.Treatment); processed.ByFlagFallbackTreatment.TryGetValue("flag", out FallbackTreatment fallbackTreatment2); Assert.AreEqual("off", fallbackTreatment2.Treatment); @@ -46,7 +46,7 @@ public void Works() fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( new FallbackTreatment("on"), new Dictionary() { { "flag", "off" } }); - processed = fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration, Splitio.Enums.API.GetTreatment); + processed = fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration); Assert.AreEqual("on", processed.GlobalFallbackTreatment.Treatment); processed.ByFlagFallbackTreatment.TryGetValue("flag", out fallbackTreatment); Assert.AreEqual("off", fallbackTreatment.Treatment); @@ -54,7 +54,7 @@ public void Works() fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( new FallbackTreatment("on"), new Dictionary() { { "flag", "off" } }); - processed = fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration, Splitio.Enums.API.GetTreatment); + processed = fallbackTreatmentsValidator.validate(fallbackTreatmentsConfiguration); Assert.AreEqual("on", processed.GlobalFallbackTreatment.Treatment); processed.ByFlagFallbackTreatment.TryGetValue("flag", out fallbackTreatment); Assert.AreEqual("off", fallbackTreatment.Treatment); diff --git a/tests/Splitio-tests/Unit Tests/Matchers/UserDefinedSegmentMatcherAsyncTests.cs b/tests/Splitio-tests/Unit Tests/Matchers/UserDefinedSegmentMatcherAsyncTests.cs index 1a7a087ff..dc3770eee 100644 --- a/tests/Splitio-tests/Unit Tests/Matchers/UserDefinedSegmentMatcherAsyncTests.cs +++ b/tests/Splitio-tests/Unit Tests/Matchers/UserDefinedSegmentMatcherAsyncTests.cs @@ -1,7 +1,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using Splitio.Domain; using Splitio.Services.Cache.Classes; using Splitio.Services.Parsing; +using Splitio.Services.Tasks; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; @@ -22,7 +24,8 @@ public async Task MatchAsyncShouldReturnTrueOnMatchingSegmentWithKey() }; var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, keys); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -45,7 +48,8 @@ public async Task MatchAsyncShouldReturnFalseOnNonMatchingSegmentWithKey() }; var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, keys); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -62,7 +66,8 @@ public async Task MatchAsyncShouldReturnFalseIfSegmentEmptyWithKey() { //Arrange var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, null); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -79,7 +84,8 @@ public async Task MatchAsyncShouldReturnFalseIfCacheEmptyWithKey() { //Arrange var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -101,7 +107,8 @@ public async Task MatchAsyncShouldReturnTrueOnMatchingSegment() }; var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, keys); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -124,7 +131,8 @@ public async Task MatchAsyncShouldReturnFalseOnNonMatchingSegment() }; var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, keys); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -141,7 +149,8 @@ public async Task MatchAsyncShouldReturnFalseIfSegmentEmpty() { //Arrange var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, null); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -158,7 +167,8 @@ public async Task MatchAsyncShouldReturnFalseIfCacheEmpty() { //Arrange var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); diff --git a/tests/Splitio-tests/Unit Tests/Matchers/UserDefinedSegmentMatcherTests.cs b/tests/Splitio-tests/Unit Tests/Matchers/UserDefinedSegmentMatcherTests.cs index e35113990..07955ff8e 100644 --- a/tests/Splitio-tests/Unit Tests/Matchers/UserDefinedSegmentMatcherTests.cs +++ b/tests/Splitio-tests/Unit Tests/Matchers/UserDefinedSegmentMatcherTests.cs @@ -1,9 +1,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Splitio.Services.Parsing; +using Moq; using Splitio.Domain; -using System.Collections.Generic; -using System.Collections.Concurrent; using Splitio.Services.Cache.Classes; +using Splitio.Services.Parsing; +using Splitio.Services.Tasks; +using System.Collections.Concurrent; +using System.Collections.Generic; namespace Splitio_Tests.Unit_Tests { @@ -21,7 +23,8 @@ public void MatchShouldReturnTrueOnMatchingSegmentWithKey() }; var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, keys); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -44,7 +47,8 @@ public void MatchShouldReturnFalseOnNonMatchingSegmentWithKey() }; var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, keys); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -61,7 +65,8 @@ public void MatchShouldReturnFalseIfSegmentEmptyWithKey() { //Arrange var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, null); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -78,7 +83,8 @@ public void MatchShouldReturnFalseIfCacheEmptyWithKey() { //Arrange var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -100,7 +106,8 @@ public void MatchShouldReturnTrueOnMatchingSegment() }; var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, keys); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -123,7 +130,8 @@ public void MatchShouldReturnFalseOnNonMatchingSegment() }; var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, keys); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -140,7 +148,8 @@ public void MatchShouldReturnFalseIfSegmentEmpty() { //Arrange var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); segmentCache.AddToSegment(segmentName, null); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); @@ -157,7 +166,8 @@ public void MatchShouldReturnFalseIfCacheEmpty() { //Arrange var segmentName = "test-segment"; - var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary()); + Mock internalEventsTask = new Mock(); + var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary(), internalEventsTask.Object); var matcher = new UserDefinedSegmentMatcher(segmentName, segmentCache); diff --git a/tests/Splitio-tests/Unit Tests/SegmentFetcher/SelfRefreshingSegmentFetcherUnitTests.cs b/tests/Splitio-tests/Unit Tests/SegmentFetcher/SelfRefreshingSegmentFetcherUnitTests.cs index b2eac5b97..4bf0e94b1 100644 --- a/tests/Splitio-tests/Unit Tests/SegmentFetcher/SelfRefreshingSegmentFetcherUnitTests.cs +++ b/tests/Splitio-tests/Unit Tests/SegmentFetcher/SelfRefreshingSegmentFetcherUnitTests.cs @@ -4,10 +4,10 @@ using Splitio.Services.Cache.Classes; using Splitio.Services.Cache.Interfaces; using Splitio.Services.Client.Classes; +using Splitio.Services.Common; using Splitio.Services.SegmentFetcher.Classes; using Splitio.Services.SegmentFetcher.Interfaces; using Splitio.Services.Shared.Classes; -using Splitio.Services.Shared.Interfaces; using Splitio.Services.SplitFetcher.Interfaces; using Splitio.Services.Tasks; using System.Collections.Concurrent; @@ -23,15 +23,17 @@ public class SelfRefreshingSegmentFetcherUnitTests private static readonly string PayedSplitJson = @"{'name': 'payed','added': ['abcdz','bcadz','xzydz'],'removed': [],'since': -1,'till': 10001}"; [TestMethod] - public void InitializeSegmentNotExistent() + public async Task InitializeSegmentNotExistent() { // Arrange - var gates = new InMemoryReadinessGatesCache(); - gates.SetReady(); + Mock> eventsManager = new Mock>(); + var internalEventsTask = new InternalEventsTask(eventsManager.Object, new SplitQueue()); + var gates = new InMemoryReadinessGatesCache(internalEventsTask); + await gates.SetReadyAsync(); var apiClient = new Mock(); var apiFetcher = new ApiSegmentChangeFetcher(apiClient.Object); var segments = new ConcurrentDictionary(); - var cache = new InMemorySegmentCache(segments); + var cache = new InMemorySegmentCache(segments, internalEventsTask); var segmentsQueue = new SplitQueue(); var taskManager = new TasksManager(gates); var worker = new SegmentTaskWorker(5, segmentsQueue); diff --git a/tests/Splitio-tests/Unit Tests/SegmentFetcher/SelfRefreshingSegmentUnitTests.cs b/tests/Splitio-tests/Unit Tests/SegmentFetcher/SelfRefreshingSegmentUnitTests.cs index 228dc4c59..c192b4ed0 100644 --- a/tests/Splitio-tests/Unit Tests/SegmentFetcher/SelfRefreshingSegmentUnitTests.cs +++ b/tests/Splitio-tests/Unit Tests/SegmentFetcher/SelfRefreshingSegmentUnitTests.cs @@ -5,6 +5,7 @@ using Splitio.Services.Cache.Interfaces; using Splitio.Services.SegmentFetcher.Classes; using Splitio.Services.SplitFetcher.Interfaces; +using Splitio.Services.Tasks; using System; using System.Collections.Concurrent; using System.Threading.Tasks; @@ -22,7 +23,8 @@ public async Task FetchSegmentNullChangesFetcherResponseShouldNotUpdateCache() var statusManager = new Mock(); var apiFetcher = new ApiSegmentChangeFetcher(apiClient.Object); var segments = new ConcurrentDictionary(); - var cache = new InMemorySegmentCache(segments); + Mock internalEventsTask = new Mock(); + var cache = new InMemorySegmentCache(segments, internalEventsTask.Object); var segmentFetcher = new SelfRefreshingSegment("payed", apiFetcher, cache, statusManager.Object); apiClient @@ -44,7 +46,8 @@ public async Task FetchSegmentShouldUpdateSegmentsCache() var statusManager = new Mock(); var apiFetcher = new ApiSegmentChangeFetcher(apiClient.Object); var segments = new ConcurrentDictionary(); - var cache = new InMemorySegmentCache(segments); + Mock internalEventsTask = new Mock(); + var cache = new InMemorySegmentCache(segments, internalEventsTask.Object); var segmentFetcher = new SelfRefreshingSegment("payed", apiFetcher, cache, statusManager.Object); apiClient diff --git a/tests/Splitio-tests/Unit Tests/Tasks/InternalEvenstTaskTests.cs b/tests/Splitio-tests/Unit Tests/Tasks/InternalEvenstTaskTests.cs new file mode 100644 index 000000000..a706c5af9 --- /dev/null +++ b/tests/Splitio-tests/Unit Tests/Tasks/InternalEvenstTaskTests.cs @@ -0,0 +1,39 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Splitio.Domain; +using Splitio.Services.Common; +using Splitio.Services.EventSource.Workers; +using Splitio.Services.Shared.Classes; +using Splitio.Services.Tasks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Splitio_Tests.Unit_Tests.Tasks +{ + [TestClass] + public class InternalEvenstTaskTests + { + + [TestMethod] + public async Task TestFiringEvents() + { + //Act + SplitQueue internalEventsQueue = new SplitQueue(); + Mock> eventManager = new Mock>(); + InternalEventsTask internalEventsTask = new InternalEventsTask(eventManager.Object, internalEventsQueue); + internalEventsTask.Start(); + + await internalEventsQueue.EnqueueAsync(new SdkEventNotification(SdkInternalEvent.SdkReady, null)); + System.Threading.SpinWait.SpinUntil(() => eventManager.Invocations.Count>0, TimeSpan.FromMilliseconds(2000)); + eventManager.Verify(mock => mock.NotifyInternalEvent(SdkInternalEvent.SdkReady, null), Times.Exactly(1)); + + var metadata = new EventMetadata(SdkEventType.FlagsUpdate, new List { { "flag1" } }); + await internalEventsTask.AddToQueue(SdkInternalEvent.FlagsUpdated, metadata); + System.Threading.SpinWait.SpinUntil(() => eventManager.Invocations.Count > 1, TimeSpan.FromMilliseconds(2000)); + eventManager.Verify(mock => mock.NotifyInternalEvent(SdkInternalEvent.FlagsUpdated, metadata), Times.Exactly(1)); + + internalEventsTask.Stop(); + } + } +} diff --git a/tests/Splitio.Integration-tests/EventSourceClientTests.cs b/tests/Splitio.Integration-tests/EventSourceClientTests.cs index ba7766438..65f57d567 100644 --- a/tests/Splitio.Integration-tests/EventSourceClientTests.cs +++ b/tests/Splitio.Integration-tests/EventSourceClientTests.cs @@ -370,7 +370,9 @@ private static (IEventSourceClient, BlockingCollection, var sseHttpClient = new SplitioHttpClient("api-key", config, new Dictionary()); var telemetryRuntimeProducer = new InMemoryTelemetryStorage(); var notificationManagerKeeper = new NotificationManagerKeeper(telemetryRuntimeProducer, streamingStatusQueue); - var statusManager = new InMemoryReadinessGatesCache(); + EventsManager eventsManager = new EventsManager(new EventsManagerConfig(), new EventDelivery()); + var internalEventsTask = new InternalEventsTask(eventsManager, new SplitQueue()); + var statusManager = new InMemoryReadinessGatesCache(internalEventsTask); var tasksManager = new TasksManager(statusManager); var task = tasksManager.NewOnTimeTask(Enums.Task.SSEConnect); diff --git a/tests/Splitio.Integration-tests/PollingClientTests.cs b/tests/Splitio.Integration-tests/PollingClientTests.cs index 37c97ee71..306b7b8d4 100644 --- a/tests/Splitio.Integration-tests/PollingClientTests.cs +++ b/tests/Splitio.Integration-tests/PollingClientTests.cs @@ -161,7 +161,7 @@ public void GetTreatment_WithtBUR_ReturnsTimeOutException() var apikey = "apikey5"; var splitFactory = new SplitFactory(apikey, configurations); - var client = splitFactory.Client(); + var client = (SplitClient)splitFactory.Client(); // Act. var exceptionMessage = ""; @@ -400,7 +400,7 @@ public void Telemetry_ValidatesConfigInitAndStats() try { - client.BlockUntilReady(0); + client.BlockUntilReady(1); } catch { @@ -412,7 +412,8 @@ public void Telemetry_ValidatesConfigInitAndStats() // Assert. Assert.AreEqual("on", result); - + + System.Threading.SpinWait.SpinUntil(() => (GetMetricsConfigSentBackend(httpClientMock) != null) , TimeSpan.FromMilliseconds(1000)); var sentConfig = GetMetricsConfigSentBackend(httpClientMock); Assert.IsNotNull(sentConfig); Assert.AreEqual(configurations.StreamingEnabled, sentConfig.StreamingEnabled); diff --git a/tests/Splitio.Integration-tests/StreamingClientTests.cs b/tests/Splitio.Integration-tests/StreamingClientTests.cs index 7623dadd1..d9925b28d 100644 --- a/tests/Splitio.Integration-tests/StreamingClientTests.cs +++ b/tests/Splitio.Integration-tests/StreamingClientTests.cs @@ -22,6 +22,10 @@ namespace Splitio.Integration_tests public class StreamingClientTests { public static string EventSourcePath => "/eventsource"; + private bool SdkReady = false; + private bool SdkReady2 = false; + private bool SdkReady3 = false; + private bool SdkUpdate = false; [TestMethod] public void GetTreatment_SplitUpdate_ShouldFetch() @@ -56,18 +60,27 @@ public void GetTreatment_SplitUpdate_ShouldFetch() SegmentsRefreshRate = 3000, AuthServiceURL = $"{url}/api/auth", StreamingServiceURL = $"{url}{EventSourcePath}", - StreamingEnabled = true + StreamingEnabled = true, + Logger = SplitLogger.Console(Level.Debug) }; var apikey = "apikey1"; - var splitFactory = new SplitFactory(apikey, config); - var client = splitFactory.Client(); + var client = (SplitClient)splitFactory.Client(); + client.SdkReady += sdkReady_callback; + client.SdkReady += sdkReady_callback2; + client.SdkUpdate += sdkUpdate_callback; client.BlockUntilReady(10000); var result = EvaluateWithDelay("admin", "push_test", "after_fetch", client); + client.SdkReady += sdkReady_callback3; + Assert.AreEqual("after_fetch", result); + Assert.IsTrue(SdkReady); + Assert.IsTrue(SdkReady2); + Assert.IsTrue(SdkReady3); + Assert.IsTrue(SdkUpdate); client.Destroy(); } @@ -112,13 +125,11 @@ public void GetTreatment_SplitUpdate_ShouldNotFetch() var splitFactory = new SplitFactory(apikey, config); var client = splitFactory.Client(); - client.BlockUntilReady(10000); var result = EvaluateWithDelay("admin", "push_test", "on", client); Assert.AreEqual("on", result); - client.Destroy(); } } @@ -169,7 +180,6 @@ public void GetTreatment_SplitKill_ShouldFetch() Thread.Sleep(5000); var result = client.GetTreatment("admin", "push_test"); - Assert.AreEqual("after_fetch", result); client.Destroy(); @@ -765,5 +775,25 @@ public static string EvaluateWithDelay(string key, string splitName, string expe return result; } + + private void sdkReady_callback(object sender, EventMetadata metadata) + { + SdkReady = true; + } + + private void sdkReady_callback2(object sender, EventMetadata metadata) + { + SdkReady2 = true; + } + + private void sdkReady_callback3(object sender, EventMetadata metadata) + { + SdkReady3 = true; + } + + private void sdkUpdate_callback(object sender, EventMetadata metadata) + { + SdkUpdate = true; + } } } diff --git a/tests/Splitio.TestSupport/Samples/SampleTest.cs b/tests/Splitio.TestSupport/Samples/SampleTest.cs index ecaee10a3..9633110a7 100644 --- a/tests/Splitio.TestSupport/Samples/SampleTest.cs +++ b/tests/Splitio.TestSupport/Samples/SampleTest.cs @@ -1,6 +1,4 @@ -using Moq; -using Splitio.Services.Client.Classes; -using Splitio.Services.Logger; +using Splitio.Services.Client.Classes; using Xunit; namespace Splitio.TestSupport.Samples @@ -11,7 +9,7 @@ public class SampleTest public SampleTest() { - splitClient = new SplitClientForTest(); + splitClient = new SplitClientForTest(new ConfigurationOptions()); } [Theory] diff --git a/tests/Splitio.TestSupport/SplitClientForTest.cs b/tests/Splitio.TestSupport/SplitClientForTest.cs index ddffe2eb0..9f453fc19 100644 --- a/tests/Splitio.TestSupport/SplitClientForTest.cs +++ b/tests/Splitio.TestSupport/SplitClientForTest.cs @@ -1,5 +1,4 @@ using Splitio.Domain; -using Splitio.Services.Impressions.Classes; using System.Collections.Generic; using System.Threading.Tasks; @@ -9,8 +8,9 @@ public class SplitClientForTest : SplitClient { private readonly Dictionary _tests; - public SplitClientForTest() : base("SplitClientForTest", new FallbackTreatmentCalculator(new FallbackTreatmentsConfiguration())) + public SplitClientForTest(ConfigurationOptions config) : base("SplitClientForTest") { + BuildFallbackCalculator(config.FallbackTreatments); _tests = new Dictionary(); } diff --git a/tests/Splitio.Tests.Common/BaseIntegrationTests.cs b/tests/Splitio.Tests.Common/BaseIntegrationTests.cs index 4bfa02db6..0efa18c03 100644 --- a/tests/Splitio.Tests.Common/BaseIntegrationTests.cs +++ b/tests/Splitio.Tests.Common/BaseIntegrationTests.cs @@ -1155,6 +1155,31 @@ public void GetTreatmentsByFlagSet_WithWrongFlagSets() #region FallbackTreatments [TestMethod] + public void FallbackTreatmentsIgnored_WithInvalidConfig() + { + var features = new List { "feature2", "feature" }; + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-gl^^obal", "\"prop\":\"global\""), new Dictionary() { { "feature", new FallbackTreatment("off-l###ocal", "\"prop\":\"local\"") } }); + var impressionListener = new IntegrationTestsImpressionListener(50); + var configurations = GetConfigurationOptions(impressionListener: impressionListener); + configurations.FallbackTreatments = fallbackTreatmentsConfiguration; + + var apikey = "base-apikey10"; + + var splitFactory = new SplitFactory(apikey, configurations); + var client = splitFactory.Client(); + + client.BlockUntilReady(10000); + + // Act. + var result = client.GetTreatment("nico_test", "feature"); + var result2 = client.GetTreatment("nico_test", "feature2"); + client.Destroy(); + + // Assert. + Assert.AreEqual("control", result); + Assert.AreEqual("control", result2); + } + [TestMethod] public void FallbackTreatments_WhenFeatureDoesNotExist() { var features = new List { "feature2", "feature" };