diff --git a/Splitio.Redis/Services/Client/Classes/RedisClient.cs b/Splitio.Redis/Services/Client/Classes/RedisClient.cs index cb9acdc0..746d6319 100644 --- a/Splitio.Redis/Services/Client/Classes/RedisClient.cs +++ b/Splitio.Redis/Services/Client/Classes/RedisClient.cs @@ -122,7 +122,7 @@ private void BuildImpressionManager() { var shouldCalculatePreviousTime = _config.ImpressionsMode == ImpressionsMode.Optimized; - _impressionsManager = new ImpressionsManager(_impressionsLog, _customerImpressionListener, _impressionsCounter, shouldCalculatePreviousTime, _config.ImpressionsMode, null, _tasksManager, _uniqueKeysTracker, _impressionsObserver, _config.LabelsEnabled); + _impressionsManager = new ImpressionsManager(_impressionsLog, _customerImpressionListener, _impressionsCounter, shouldCalculatePreviousTime, _config.ImpressionsMode, null, _tasksManager, _uniqueKeysTracker, _impressionsObserver, _config.LabelsEnabled, _propertiesValidator); } private void BuildEventLog() diff --git a/src/Splitio/Services/Client/Classes/JSONFileClient.cs b/src/Splitio/Services/Client/Classes/JSONFileClient.cs index a3aacc2f..0efa534f 100644 --- a/src/Splitio/Services/Client/Classes/JSONFileClient.cs +++ b/src/Splitio/Services/Client/Classes/JSONFileClient.cs @@ -62,7 +62,7 @@ public JSONFileClient(string splitsFilePath, _uniqueKeysTracker = new NoopUniqueKeysTracker(); _impressionsCounter = new NoopImpressionsCounter(); _impressionsObserver = new NoopImpressionsObserver(); - _impressionsManager = impressionsManager ?? new ImpressionsManager(impressionsLog, null, _impressionsCounter, false, ImpressionsMode.Debug, null, _tasksManager, _uniqueKeysTracker, _impressionsObserver, isLabelsEnabled); + _impressionsManager = impressionsManager ?? new ImpressionsManager(impressionsLog, null, _impressionsCounter, false, ImpressionsMode.Debug, null, _tasksManager, _uniqueKeysTracker, _impressionsObserver, isLabelsEnabled, _propertiesValidator); BuildClientExtension(); } diff --git a/src/Splitio/Services/Client/Classes/LocalhostClient.cs b/src/Splitio/Services/Client/Classes/LocalhostClient.cs index b113952b..b232c817 100644 --- a/src/Splitio/Services/Client/Classes/LocalhostClient.cs +++ b/src/Splitio/Services/Client/Classes/LocalhostClient.cs @@ -67,7 +67,7 @@ public LocalhostClient(ConfigurationOptions configurationOptions) : base("localh _uniqueKeysTracker = new NoopUniqueKeysTracker(); _impressionsCounter = new NoopImpressionsCounter(); _impressionsObserver = new NoopImpressionsObserver(); - _impressionsManager = new ImpressionsManager(null, null, _impressionsCounter, false, ImpressionsMode.Debug, null, _tasksManager, _uniqueKeysTracker, _impressionsObserver, false); + _impressionsManager = new ImpressionsManager(null, null, _impressionsCounter, false, ImpressionsMode.Debug, null, _tasksManager, _uniqueKeysTracker, _impressionsObserver, false, _propertiesValidator); BuildClientExtension(); diff --git a/src/Splitio/Services/Client/Classes/SelfRefreshingClient.cs b/src/Splitio/Services/Client/Classes/SelfRefreshingClient.cs index 041b0e65..efcfec64 100644 --- a/src/Splitio/Services/Client/Classes/SelfRefreshingClient.cs +++ b/src/Splitio/Services/Client/Classes/SelfRefreshingClient.cs @@ -159,7 +159,7 @@ private void BuildImpressionsObserver() private void BuildImpressionManager() { - _impressionsManager = new ImpressionsManager(_impressionsLog, _customerImpressionListener, _impressionsCounter, true, _config.ImpressionsMode, _telemetryRuntimeProducer, _tasksManager, _uniqueKeysTracker, _impressionsObserver, _config.LabelsEnabled); + _impressionsManager = new ImpressionsManager(_impressionsLog, _customerImpressionListener, _impressionsCounter, true, _config.ImpressionsMode, _telemetryRuntimeProducer, _tasksManager, _uniqueKeysTracker, _impressionsObserver, _config.LabelsEnabled, _propertiesValidator); } private void BuildEventLog() diff --git a/src/Splitio/Services/Client/Classes/SplitClient.cs b/src/Splitio/Services/Client/Classes/SplitClient.cs index 9e677de8..b18fe01f 100644 --- a/src/Splitio/Services/Client/Classes/SplitClient.cs +++ b/src/Splitio/Services/Client/Classes/SplitClient.cs @@ -34,7 +34,7 @@ public abstract class SplitClient : ISplitClient protected readonly IKeyValidator _keyValidator; protected readonly ISplitNameValidator _splitNameValidator; protected readonly IEventTypeValidator _eventTypeValidator; - protected readonly IPropertiesValidator _eventPropertiesValidator; + protected readonly IPropertiesValidator _propertiesValidator; protected readonly IWrapperAdapter _wrapperAdapter; protected readonly IConfigService _configService; protected readonly IFlagSetsValidator _flagSetsValidator; @@ -71,7 +71,7 @@ public SplitClient(string apikey) _keyValidator = new KeyValidator(); _splitNameValidator = new SplitNameValidator(); _eventTypeValidator = new EventTypeValidator(); - _eventPropertiesValidator = new PropertiesValidator(); + _propertiesValidator = new PropertiesValidator(); _factoryInstantiationsService = FactoryInstantiationsService.Instance(); _flagSetsValidator = new FlagSetsValidator(); _configService = new ConfigService(_wrapperAdapter, _flagSetsValidator, new SdkMetadataValidator()); @@ -412,7 +412,7 @@ protected void BuildImpressionsCounter(BaseConfig config) protected void BuildClientExtension() { - _clientExtensionService = new ClientExtensionService(_blockUntilReadyService, _statusManager, _keyValidator, _splitNameValidator, _telemetryEvaluationProducer, _eventTypeValidator, _eventPropertiesValidator, _trafficTypeValidator, _flagSetsValidator, _flagSetsFilter); + _clientExtensionService = new ClientExtensionService(_blockUntilReadyService, _statusManager, _keyValidator, _splitNameValidator, _telemetryEvaluationProducer, _eventTypeValidator, _propertiesValidator, _trafficTypeValidator, _flagSetsValidator, _flagSetsFilter); } protected void BuildFlagSetsFilter(HashSet sets) @@ -520,13 +520,13 @@ private List GetTreatmentsByFlagSets(Enums.API method, Key key, } } - private List BuildAndGetImpressions(List treatments, Key key) + private List BuildAndGetImpressions(List treatments, Key key, Dictionary properties = null) { var impressions = new List(); foreach (var treatment in treatments) { - var impression = _impressionsManager.Build(treatment, key); + var impression = _impressionsManager.Build(treatment, key, properties); if (impression != null) impressions.Add(impression); } diff --git a/src/Splitio/Services/Impressions/Classes/ImpressionsManager.cs b/src/Splitio/Services/Impressions/Classes/ImpressionsManager.cs index 0e5bdb23..1ca7684a 100644 --- a/src/Splitio/Services/Impressions/Classes/ImpressionsManager.cs +++ b/src/Splitio/Services/Impressions/Classes/ImpressionsManager.cs @@ -1,5 +1,7 @@ -using Splitio.Domain; +using Newtonsoft.Json; +using Splitio.Domain; using Splitio.Services.Impressions.Interfaces; +using Splitio.Services.InputValidation.Interfaces; using Splitio.Services.Logger; using Splitio.Services.Shared.Classes; using Splitio.Services.Tasks; @@ -24,6 +26,7 @@ public class ImpressionsManager : IImpressionsManager private readonly ITasksManager _taskManager; private readonly ImpressionsMode _impressionsMode; private readonly IUniqueKeysTracker _uniqueKeysTracker; + private readonly IPropertiesValidator _propertiesValidator; private readonly bool _addPreviousTime; private readonly bool _labelsEnabled; @@ -36,7 +39,8 @@ public ImpressionsManager(IImpressionsLog impressionsLog, ITasksManager taskManager, IUniqueKeysTracker uniqueKeysTracker, IImpressionsObserver impressionsObserver, - bool labelsEnabled) + bool labelsEnabled, + IPropertiesValidator propertiesValidator) { _impressionsLog = impressionsLog; _customerImpressionListener = customerImpressionListener; @@ -48,14 +52,21 @@ public ImpressionsManager(IImpressionsLog impressionsLog, _impressionsMode = impressionsMode; _uniqueKeysTracker = uniqueKeysTracker; _labelsEnabled = labelsEnabled; + _propertiesValidator = propertiesValidator; } #region Public Methods - public KeyImpression Build(TreatmentResult result, Key key) + public KeyImpression Build(TreatmentResult result, Key key, Dictionary properties) { if (Labels.SplitNotFound.Equals(result.Label)) return null; var impression = new KeyImpression(key.matchingKey, result.FeatureFlagName, result.Treatment, result.ImpTime, result.ChangeNumber, _labelsEnabled ? result.Label : null, key.bucketingKeyHadValue ? key.bucketingKey : null, result.ImpressionsDisabled); + + var validatorResult = _propertiesValidator.IsValid(properties); + if (validatorResult.Success) + { + impression.Properties = JsonConvert.SerializeObject(validatorResult.Value); + } try { @@ -64,19 +75,23 @@ public KeyImpression Build(TreatmentResult result, Key key) { _impressionsCounter.Inc(result.FeatureFlagName, result.ImpTime); _uniqueKeysTracker.Track(key.matchingKey, result.FeatureFlagName); - } else if (_impressionsMode == ImpressionsMode.Debug) - { - // In DEBUG mode we should calculate the pt only. - - ShouldCalculatePreviousTime(impression); - } else if (_impressionsMode == ImpressionsMode.Optimized) + } + else if (string.IsNullOrEmpty(impression.Properties)) { - // In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions. - - ShouldCalculatePreviousTime(impression); - if (impression.PreviousTime.HasValue) - _impressionsCounter.Inc(result.FeatureFlagName, result.ImpTime); - impression.Optimized = ShouldQueueImpression(impression); + switch (_impressionsMode) + { + case ImpressionsMode.Debug: + // In DEBUG mode we should calculate the pt only. + ShouldCalculatePreviousTime(impression); + break; + case ImpressionsMode.Optimized: + // In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions. + ShouldCalculatePreviousTime(impression); + if (impression.PreviousTime.HasValue) + _impressionsCounter.Inc(result.FeatureFlagName, result.ImpTime); + impression.Optimized = ShouldQueueImpression(impression); + break; + } } } catch (Exception ex) @@ -165,20 +180,26 @@ private bool GetImpressionsToTrack(List impressions, out List !i.ImpressionsDisabled) .ToList(); - if (filteredImpressions.Count == 0) return false; + if (filteredImpressions.Count == 0) + { + return false; + } switch (_impressionsMode) { case ImpressionsMode.Debug: - impressionsToTrack.AddRange(filteredImpressions); + impressionsToTrack = filteredImpressions; break; case ImpressionsMode.Optimized: default: - impressionsToTrack.AddRange(filteredImpressions.Where(i => i.Optimized).ToList()); - var deduped = filteredImpressions.Count - impressionsToTrack.Count; - _telemetryRuntimeProducer?.RecordImpressionsStats(ImpressionsEnum.ImpressionsDeduped, deduped); + impressionsToTrack = filteredImpressions + .Where(i => i.Optimized || !string.IsNullOrEmpty(i.Properties)) + .ToList(); + + _telemetryRuntimeProducer?.RecordImpressionsStats(ImpressionsEnum.ImpressionsDeduped, filteredImpressions.Count - impressionsToTrack.Count); break; } + return true; } diff --git a/src/Splitio/Services/Impressions/Interfaces/IImpressionsManager.cs b/src/Splitio/Services/Impressions/Interfaces/IImpressionsManager.cs index 8ce51b73..a194c734 100644 --- a/src/Splitio/Services/Impressions/Interfaces/IImpressionsManager.cs +++ b/src/Splitio/Services/Impressions/Interfaces/IImpressionsManager.cs @@ -6,7 +6,7 @@ namespace Splitio.Services.Impressions.Interfaces { public interface IImpressionsManager { - KeyImpression Build(TreatmentResult treatmentResult, Key key); + KeyImpression Build(TreatmentResult treatmentResult, Key key, Dictionary properties); void Track(List impressions); Task TrackAsync(List impressions); } diff --git a/src/Splitio/Services/InputValidation/Interfaces/IEventPropertiesValidator.cs b/src/Splitio/Services/InputValidation/Interfaces/IPropertiesValidator.cs similarity index 100% rename from src/Splitio/Services/InputValidation/Interfaces/IEventPropertiesValidator.cs rename to src/Splitio/Services/InputValidation/Interfaces/IPropertiesValidator.cs diff --git a/tests/Splitio-tests/Unit Tests/Client/SplitClientAsyncTests.cs b/tests/Splitio-tests/Unit Tests/Client/SplitClientAsyncTests.cs index d532bec5..8b342afe 100644 --- a/tests/Splitio-tests/Unit Tests/Client/SplitClientAsyncTests.cs +++ b/tests/Splitio-tests/Unit Tests/Client/SplitClientAsyncTests.cs @@ -72,7 +72,7 @@ public async Task GetTreatment_WhenNameDoesntExist_ReturnsControl() // Assert Assert.AreEqual("control", result); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Never); } @@ -89,7 +89,7 @@ public async Task GetTreatment_WhenExist_ReturnsOn() .ReturnsAsync(new List { new TreatmentResult("feature_flag_test", Labels.DefaultRule, "on", false, 1000, null) }); _impressionsManager - .Setup(mock => mock.Build(It.IsAny(), It.IsAny())) + .Setup(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(new KeyImpression()); // Act @@ -100,7 +100,7 @@ public async Task GetTreatment_WhenExist_ReturnsOn() _splitClient.Destroy(); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Once); } #endregion @@ -123,7 +123,7 @@ public async Task GetTreatments_WithEmptyKey_ShouldReturnControl() Assert.AreEqual("control", res.Value); } - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Never); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Never); } @@ -184,7 +184,7 @@ public async Task GetTreatments_ShouldReturnTreatments() }); _impressionsManager - .Setup(mock => mock.Build(It.IsAny(), It.IsAny())) + .Setup(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(new KeyImpression()); _blockUntilReadyService @@ -201,7 +201,7 @@ public async Task GetTreatments_ShouldReturnTreatments() var resultOff = result[parsedSplitOff.name]; Assert.AreEqual("off", resultOff); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Exactly(2)); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Exactly(2)); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Once); } @@ -229,7 +229,7 @@ public async Task GetTreatments_WhenNameDoesntExist_ReturnsControl() Assert.IsNull(res.Value.Config); } - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Never); } #endregion @@ -278,7 +278,7 @@ public async Task GetTreatmentWithConfig_ShouldReturnOnWithConfig() .ReturnsAsync(new List { new TreatmentResult(feature, Labels.DefaultRule, treatmentExpected, false, null, configExpected) }); _impressionsManager - .Setup(mock => mock.Build(It.IsAny(), It.IsAny())) + .Setup(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(new KeyImpression()); // Act @@ -288,7 +288,7 @@ public async Task GetTreatmentWithConfig_ShouldReturnOnWithConfig() Assert.AreEqual(treatmentExpected, result.Treatment); Assert.AreEqual(configExpected, result.Config); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Once); } @@ -320,7 +320,7 @@ public async Task GetTreatmentWithConfig_WhenNotMach_ShouldReturnDefaultTreatmen .ReturnsAsync(new List { new TreatmentResult(feature, "label", defaultTreatment, false, null, configExpected) }); _impressionsManager - .Setup(mock => mock.Build(It.IsAny(), It.IsAny())) + .Setup(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(new KeyImpression()); // Act @@ -330,7 +330,7 @@ public async Task GetTreatmentWithConfig_WhenNotMach_ShouldReturnDefaultTreatmen Assert.AreEqual(defaultTreatment, result.Treatment); Assert.AreEqual(configExpected, result.Config); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Once); } @@ -357,7 +357,7 @@ public async Task GetTreatmentWithConfig_WhenConfigIsNull_ShouldReturnOn() .Returns(true); _impressionsManager - .Setup(mock => mock.Build(It.IsAny(), It.IsAny())) + .Setup(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(new KeyImpression()); // Act @@ -367,7 +367,7 @@ public async Task GetTreatmentWithConfig_WhenConfigIsNull_ShouldReturnOn() Assert.AreEqual(treatmentExpected, result.Treatment); Assert.IsNull(result.Config); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Once); } @@ -399,7 +399,7 @@ public async Task GetTreatmentWithConfig_WhenIsKilled_ShouldReturnDefaultTreatme .ReturnsAsync(new List { new TreatmentResult(feature, "label", defaultTreatment, false, null, configExpected) }); _impressionsManager - .Setup(mock => mock.Build(It.IsAny(), It.IsAny())) + .Setup(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(new KeyImpression()); // Act @@ -409,7 +409,7 @@ public async Task GetTreatmentWithConfig_WhenIsKilled_ShouldReturnDefaultTreatme Assert.AreEqual(defaultTreatment, result.Treatment); Assert.AreEqual(configExpected, result.Config); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Once); } @@ -441,7 +441,7 @@ public async Task GetTreatmentWithConfig_WhenTrafficAllocationIsSmallerThanBucke .ReturnsAsync(new List { new TreatmentResult(feature, "label", defaultTreatment, false, config: configExpected) }); _impressionsManager - .Setup(mock => mock.Build(It.IsAny(), It.IsAny())) + .Setup(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(new KeyImpression()); // Act @@ -451,7 +451,7 @@ public async Task GetTreatmentWithConfig_WhenTrafficAllocationIsSmallerThanBucke Assert.AreEqual(defaultTreatment, result.Treatment); Assert.AreEqual(configExpected, result.Config); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Once); } @@ -567,7 +567,7 @@ public async Task GetTreatmentWithConfig_WhenNameDoesntExist_ReturnsControl() Assert.AreEqual("control", result.Treatment); Assert.IsNull(result.Config); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Never); } #endregion @@ -591,7 +591,7 @@ public async Task GetTreatmentsWithConfig_WithEmptyKey_ShouldReturnControl() Assert.IsNull(res.Value.Config); } - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Never); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Never); } @@ -652,7 +652,7 @@ public async Task GetTreatmentsWithConfig_ShouldReturnTreatmentsWithConfigs() }); _impressionsManager - .Setup(mock => mock.Build(It.IsAny(), It.IsAny())) + .Setup(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(new KeyImpression()); _blockUntilReadyService @@ -671,7 +671,7 @@ public async Task GetTreatmentsWithConfig_ShouldReturnTreatmentsWithConfigs() Assert.AreEqual("off", resultOff.Treatment); Assert.AreEqual(configExpectedOff, resultOff.Config); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Exactly(2)); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Exactly(2)); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Once); } @@ -699,7 +699,7 @@ public async Task GetTreatmentsWithConfig_WhenNameDoesntExist_ReturnsControl() Assert.IsNull(res.Value.Config); } - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.TrackAsync(It.IsAny>()), Times.Never); } #endregion diff --git a/tests/Splitio-tests/Unit Tests/Client/SplitClientUnitTests.cs b/tests/Splitio-tests/Unit Tests/Client/SplitClientUnitTests.cs index 56441467..f0c2cfb8 100644 --- a/tests/Splitio-tests/Unit Tests/Client/SplitClientUnitTests.cs +++ b/tests/Splitio-tests/Unit Tests/Client/SplitClientUnitTests.cs @@ -103,7 +103,7 @@ public void GetTreatment_WhenNameDoesntExist_ReturnsControl() // Assert Assert.AreEqual("control", result); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.Track(It.IsAny>()), Times.Never); } #endregion @@ -422,7 +422,7 @@ public void GetTreatmentWithConfig_WhenNameDoesntExist_ReturnsControl() Assert.AreEqual("control", result.Treatment); Assert.IsNull(result.Config); - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.Track(It.IsAny>()), Times.Never); } #endregion @@ -563,7 +563,7 @@ public void GetTreatmentsWithConfig_WhenNameDoesntExist_ReturnsControl() Assert.IsNull(res.Value.Config); } - _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny()), Times.Once); + _impressionsManager.Verify(mock => mock.Build(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _impressionsManager.Verify(mock => mock.Track(It.IsAny>()), Times.Never); } #endregion diff --git a/tests/Splitio-tests/Unit Tests/Impressions/ImpressionsManagerTests.cs b/tests/Splitio-tests/Unit Tests/Impressions/ImpressionsManagerTests.cs index 6f8521fa..f0d3b462 100644 --- a/tests/Splitio-tests/Unit Tests/Impressions/ImpressionsManagerTests.cs +++ b/tests/Splitio-tests/Unit Tests/Impressions/ImpressionsManagerTests.cs @@ -5,6 +5,7 @@ using Splitio.Services.Cache.Interfaces; using Splitio.Services.Impressions.Classes; using Splitio.Services.Impressions.Interfaces; +using Splitio.Services.InputValidation.Interfaces; using Splitio.Services.Tasks; using Splitio.Telemetry.Domain.Enums; using Splitio.Telemetry.Storages; @@ -25,6 +26,7 @@ public class ImpressionsManagerTests private readonly Mock _telemetryRuntimeProducer; private readonly Mock _uniqueKeysTracker; private readonly Mock _statusManager; + private readonly Mock _propertiesValidator; private readonly ITasksManager _tasksManager; public ImpressionsManagerTests() @@ -36,7 +38,15 @@ public ImpressionsManagerTests() _telemetryRuntimeProducer = new Mock(); _uniqueKeysTracker = new Mock(); _statusManager = new Mock(); + _propertiesValidator = new Mock(); _tasksManager = new TasksManager(_statusManager.Object); + + _propertiesValidator + .Setup(mock => mock.IsValid(It.IsAny>())) + .Returns(new PropertiesValidatorResult + { + Success = false, + }); } [TestMethod] @@ -53,7 +63,7 @@ public void BuildImpressionWithOptimizedAndWithPreviousTime() .Returns(ptTime); // Act. - var result = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")); + var result = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null); // Assert. Assert.AreEqual("matching-key", result.KeyName); @@ -80,7 +90,7 @@ public void BuildImpressionWithDebugAndWithPreviousTime() .Returns((long?)null); // Act. - var result = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")); + var result = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null); // Assert. Assert.AreEqual("matching-key", result.KeyName); @@ -103,7 +113,7 @@ public void BuildImpressionWithDebugAndWithoutPreviousTime() var impTime = CurrentTimeHelper.CurrentTimeMillis(); // Act. - var result = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")); + var result = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null); // Assert. Assert.AreEqual("matching-key", result.KeyName); @@ -126,7 +136,7 @@ public void BuildAndTrack() var impTime = CurrentTimeHelper.CurrentTimeMillis(); // Act. - var imp = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")); + var imp = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null); impressionsManager.Track(new List { imp }); // Assert. @@ -147,7 +157,7 @@ public void BuildAndTrackWithoutCustomerListener() var impTime = CurrentTimeHelper.CurrentTimeMillis(); // Act. - var imp = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")); + var imp = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null); impressionsManager.Track(new List { imp }); // Assert. @@ -273,10 +283,10 @@ public void TrackWithoutCustomerListener_Optimized() var impTime = CurrentTimeHelper.CurrentTimeMillis(); var impressions = new List { - impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")) + impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null) }; var optimizedImpressions = impressions.Where(i => ImpressionsManager.ShouldQueueImpression(i)).ToList(); @@ -305,10 +315,10 @@ public void TrackWithoutCustomerListener_Debug() var impTime = CurrentTimeHelper.CurrentTimeMillis(); var impressions = new List { - impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")) + impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null) }; // Act. @@ -332,7 +342,7 @@ public void BuildImpressionWithNoneMode() var impTime = CurrentTimeHelper.CurrentTimeMillis(); // Act. - var result = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")); + var result = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null); // Assert. Assert.AreEqual("matching-key", result.KeyName); @@ -355,7 +365,7 @@ public void BuildAndTrackWithNoneMode() var impTime = CurrentTimeHelper.CurrentTimeMillis(); // Act. - var imp = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")); + var imp = impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null); impressionsManager.Track(new List { imp }); // Assert. @@ -374,10 +384,10 @@ public void TrackWithNoneMode() var impressions = new List { - impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")), - impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key")) + impressionsManager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null), + impressionsManager.Build(new TreatmentResult("feature-2", "label-2", "off", false, 432543, impTime: impTime), new Key("matching-key-2", "bucketing-key"), null) }; // Act. @@ -402,8 +412,8 @@ public async Task BuildAndTrackAsyncOptimized() // Act. await manager.TrackAsync(new List { - manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")), - manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")) + manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null), + manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null) }); // Assert. @@ -430,8 +440,8 @@ public async Task BuildAndTrackAsyncNone() // Act. await manager.TrackAsync(new List { - manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")), - manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")) + manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null), + manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null) }); // Assert. @@ -459,8 +469,8 @@ public async Task BuildAndTrackAsyncDebug() // Act. await manager.TrackAsync(new List { - manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")), - manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key")) + manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null), + manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), null) }); // Assert. @@ -472,7 +482,89 @@ await manager.TrackAsync(new List _telemetryRuntimeProducer.Verify(mock => mock.RecordImpressionsStats(ImpressionsEnum.ImpressionsDeduped, 0), Times.Never); } - private ImpressionsManager GetManager(IImpressionListener impressionListener, ImpressionsMode mode = ImpressionsMode.Optimized, bool addPt = true, bool labelsEnabled = false, IImpressionsObserver impressionsObserver = null) + [TestMethod] + public async Task BuildAndTrackAsyncDebugdWithProperties() + { + // Arrange. + _propertiesValidator + .SetupSequence(mock => mock.IsValid(It.IsAny>())) + .Returns(new PropertiesValidatorResult + { + Success = true, + Value = new Dictionary { { "city", "mdp" } } + }) + .Returns(new PropertiesValidatorResult + { + Success = true, + Value = new Dictionary{ { "city", "tandil" } } + }); + + var manager = GetManager(_customerImpressionListener.Object, ImpressionsMode.Debug, addPt: true, labelsEnabled: true); + var impTime = CurrentTimeHelper.CurrentTimeMillis(); + + _impressionsObserver + .SetupSequence(mock => mock.TestAndSet(It.IsAny())) + .Returns((long?)null) + .Returns(100); + + // Act. + var imp1 = manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), new Dictionary { { "city", "mdp" } }); + var imp2 = manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), new Dictionary { { "city", "tandil" } }); + await manager.TrackAsync(new List{ imp1, imp2 }); + + // Assert. + Assert.AreEqual("{\"city\":\"mdp\"}", imp1.Properties); + Assert.AreEqual("{\"city\":\"tandil\"}", imp2.Properties); + _impressionsCounter.Verify(mock => mock.Inc("feature", It.IsAny()), Times.Never); + _impressionsObserver.Verify(mock => mock.TestAndSet(It.IsAny()), Times.Never); + _impressionsLog.Verify(mock => mock.LogAsync(It.IsAny>()), Times.Once); + _telemetryRuntimeProducer.Verify(mock => mock.RecordImpressionsStats(ImpressionsEnum.ImpressionsQueued, 2), Times.Once); + _telemetryRuntimeProducer.Verify(mock => mock.RecordImpressionsStats(ImpressionsEnum.ImpressionsDropped, 0), Times.Once); + _telemetryRuntimeProducer.Verify(mock => mock.RecordImpressionsStats(ImpressionsEnum.ImpressionsDeduped, 0), Times.Never); + } + + [TestMethod] + public async Task BuildAndTrackAsyncOptimizedWithProperties() + { + // Arrange. + _propertiesValidator + .SetupSequence(mock => mock.IsValid(It.IsAny>())) + .Returns(new PropertiesValidatorResult + { + Success = true, + Value = new Dictionary { { "city", "mdp" } } + }) + .Returns(new PropertiesValidatorResult + { + Success = true, + Value = new Dictionary{ { "city", "tandil" } } + }); + + var manager = GetManager(_customerImpressionListener.Object, ImpressionsMode.Optimized, addPt: true, labelsEnabled: true); + var impTime = CurrentTimeHelper.CurrentTimeMillis(); + + _impressionsObserver + .SetupSequence(mock => mock.TestAndSet(It.IsAny())) + .Returns((long?)null) + .Returns(100); + + // Act. + var imp1 = manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), new Dictionary { { "city", "mdp" } }); + var imp2 = manager.Build(new TreatmentResult("feature", "label", "off", false, 432543, impTime: impTime), new Key("matching-key", "bucketing-key"), new Dictionary { { "city", "tandil" } }); + await manager.TrackAsync(new List{ imp1, imp2 }); + + // Assert. + Assert.AreEqual("{\"city\":\"mdp\"}", imp1.Properties); + Assert.AreEqual("{\"city\":\"tandil\"}", imp2.Properties); + _impressionsCounter.Verify(mock => mock.Inc("feature", It.IsAny()), Times.Never); + _impressionsObserver.Verify(mock => mock.TestAndSet(It.IsAny()), Times.Never); + _impressionsLog.Verify(mock => mock.LogAsync(It.IsAny>()), Times.Once); + _telemetryRuntimeProducer.Verify(mock => mock.RecordImpressionsStats(ImpressionsEnum.ImpressionsQueued, 2), Times.Once); + _telemetryRuntimeProducer.Verify(mock => mock.RecordImpressionsStats(ImpressionsEnum.ImpressionsDropped, 0), Times.Once); + _telemetryRuntimeProducer.Verify(mock => mock.RecordImpressionsStats(ImpressionsEnum.ImpressionsDeduped, 0), Times.Once); + } + + private ImpressionsManager GetManager(IImpressionListener impressionListener, ImpressionsMode mode = ImpressionsMode.Optimized, bool addPt = true, bool labelsEnabled = false, IImpressionsObserver impressionsObserver = null, IPropertiesValidator propertiesValidator = null) { return new ImpressionsManager(_impressionsLog.Object, impressionListener, @@ -483,7 +575,8 @@ private ImpressionsManager GetManager(IImpressionListener impressionListener, Im _tasksManager, _uniqueKeysTracker.Object, impressionsObserver ?? _impressionsObserver.Object, - labelsEnabled); + labelsEnabled, + propertiesValidator ?? _propertiesValidator.Object); } } }