From 2d7022e5d5b5541f6013c293b4d1724a02c44fe9 Mon Sep 17 00:00:00 2001 From: configcat Date: Mon, 25 Nov 2019 23:36:30 +0100 Subject: [PATCH] sdk improvements --- README.md | 6 +- ...> BasicConfigCatClientIntegrationTests.cs} | 51 +-- .../BasicConfigEvaluatorTests.cs | 51 +++ .../ConfigCat.Client.Tests.csproj | 25 +- .../ConfigCatClientTests.cs | 4 +- ...orTests.cs => ConfigEvaluatorTestsBase.cs} | 137 +++----- .../ConfigServiceTests.cs | 29 +- .../HttpConfigFetcherTests.cs | 4 +- src/ConfigCat.Client.Tests/LoggerTests.cs | 67 ++-- src/ConfigCat.Client.Tests/MyCounterLogger.cs | 43 +++ src/ConfigCat.Client.Tests/MyLogger.cs | 18 - .../NumericConfigEvaluatorTests.cs | 12 + .../ProjectConfigTests.cs | 2 +- .../SemanticVersionConfigEvaluatorTests.cs | 12 + .../data/sample_number_v2.json | 83 +++++ .../data/sample_number_v3.json | 1 + .../data/sample_semantic_v2.json | 217 ++++++++++++ .../data/sample_semantic_v3.json | 1 + .../{ => data}/sample_v2.json | 0 .../data/sample_v3.json | 332 ++++++++++++++++++ .../{ => data}/testmatrix.csv | 0 .../data/testmatrix_number.csv | 26 ++ .../data/testmatrix_semantic.csv | 36 ++ src/ConfigCatClient/ConfigCatClient.cs | 31 +- src/ConfigCatClient/ConfigCatClient.csproj | 3 +- src/ConfigCatClient/ConfigCatClientBuilder.cs | 13 + .../ConfigService/AutoPollConfigService.cs | 8 +- .../ConfigService/LazyLoadConfigService.cs | 4 +- .../ConfigService/ManualPollConfigService.cs | 4 +- .../Configuration/ConfigurationBase.cs | 24 +- .../Configuration/ConfigurationBuilderBase.cs | 3 +- .../Evaluate/EvaluateLogger.cs | 74 ++++ .../Evaluate/RolloutEvaluator.cs | 236 ++++++++++++- src/ConfigCatClient/Evaluate/Setting.cs | 40 ++- src/ConfigCatClient/HttpConfigFetcher.cs | 19 +- src/ConfigCatClient/IConfigCatClient.cs | 5 + src/ConfigCatClient/Logging/ConsoleLogger.cs | 43 ++- .../Logging/ConsoleLoggerFactory.cs | 14 - src/ConfigCatClient/Logging/ILogger.cs | 7 +- src/ConfigCatClient/Logging/ILoggerFactory.cs | 15 - src/ConfigCatClient/Logging/LogLevel.cs | 4 +- src/ConfigCatClient/Logging/LoggerBase.cs | 84 ----- src/ConfigCatClient/Logging/LoggerWrapper.cs | 58 +++ src/ConfigCatClient/Logging/NullLogger.cs | 17 - 44 files changed, 1466 insertions(+), 397 deletions(-) rename src/ConfigCat.Client.Tests/{ConfigCatClientIntegrationTests.cs => BasicConfigCatClientIntegrationTests.cs} (71%) create mode 100644 src/ConfigCat.Client.Tests/BasicConfigEvaluatorTests.cs rename src/ConfigCat.Client.Tests/{ConfigEvaluatorTests.cs => ConfigEvaluatorTestsBase.cs} (53%) create mode 100644 src/ConfigCat.Client.Tests/MyCounterLogger.cs delete mode 100644 src/ConfigCat.Client.Tests/MyLogger.cs create mode 100644 src/ConfigCat.Client.Tests/NumericConfigEvaluatorTests.cs create mode 100644 src/ConfigCat.Client.Tests/SemanticVersionConfigEvaluatorTests.cs create mode 100644 src/ConfigCat.Client.Tests/data/sample_number_v2.json create mode 100644 src/ConfigCat.Client.Tests/data/sample_number_v3.json create mode 100644 src/ConfigCat.Client.Tests/data/sample_semantic_v2.json create mode 100644 src/ConfigCat.Client.Tests/data/sample_semantic_v3.json rename src/ConfigCat.Client.Tests/{ => data}/sample_v2.json (100%) create mode 100644 src/ConfigCat.Client.Tests/data/sample_v3.json rename src/ConfigCat.Client.Tests/{ => data}/testmatrix.csv (100%) create mode 100644 src/ConfigCat.Client.Tests/data/testmatrix_number.csv create mode 100644 src/ConfigCat.Client.Tests/data/testmatrix_semantic.csv create mode 100644 src/ConfigCatClient/Evaluate/EvaluateLogger.cs delete mode 100644 src/ConfigCatClient/Logging/ConsoleLoggerFactory.cs delete mode 100644 src/ConfigCatClient/Logging/ILoggerFactory.cs delete mode 100644 src/ConfigCatClient/Logging/LoggerBase.cs create mode 100644 src/ConfigCatClient/Logging/LoggerWrapper.cs delete mode 100644 src/ConfigCatClient/Logging/NullLogger.cs diff --git a/README.md b/README.md index d1c452a8..d42f9cf5 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ client.Dispose(); ## Getting user specific setting values with Targeting Using this feature, you will be able to get different setting values for different users in your application by passing a `User Object` to the `GetValue()` function. -Read more about [Targeting here](https://docs.configcat.com/docs/advanced/targeting/). +Read more about [Targeting here](https://configcat.com/docs/advanced/targeting). ```c# User currentUser = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); @@ -87,5 +87,5 @@ If you need help how to use this SDK feel free to to contact the ConfigCat Staff Contributions are welcome. ## About ConfigCat -- [Documentation](https://docs.configcat.com) -- [Blog](https://blog.configcat.com) +- [Documentation](https://configcat.com/docs) +- [Blog](https://configcat.com/blog) diff --git a/src/ConfigCat.Client.Tests/ConfigCatClientIntegrationTests.cs b/src/ConfigCat.Client.Tests/BasicConfigCatClientIntegrationTests.cs similarity index 71% rename from src/ConfigCat.Client.Tests/ConfigCatClientIntegrationTests.cs rename to src/ConfigCat.Client.Tests/BasicConfigCatClientIntegrationTests.cs index 23aefff1..e49fb5be 100644 --- a/src/ConfigCat.Client.Tests/ConfigCatClientIntegrationTests.cs +++ b/src/ConfigCat.Client.Tests/BasicConfigCatClientIntegrationTests.cs @@ -8,59 +8,26 @@ namespace ConfigCat.Client.Tests { [TestCategory("Integration")] [TestClass] - public class ConfigCatClientIntegrationTests + public class BasicConfigCatClientIntegrationTests { private const string APIKEY = "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A"; - private static IConfigCatClient client = new ConfigCatClient(APIKEY); + private static readonly IConfigCatClient client = new ConfigCatClient(APIKEY); - [TestMethod] - public async Task GetValue_MatrixTests() - { - await ConfigEvaluatorTests.MatrixTest(AssertValue); - } + private static readonly ILogger consoleLogger = new ConsoleLogger(LogLevel.Debug); [ClassCleanup()] public static void ClassCleanup() { client?.Dispose(); - } - - private void AssertValue(string keyName, string expected, User user) - { - var k = keyName.ToLowerInvariant(); - - if (k.StartsWith("bool")) - { - var actual = client.GetValue(keyName, false, user); - - Assert.AreEqual(bool.Parse(expected), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); - } - else if (k.StartsWith("double")) - { - var actual = client.GetValue(keyName, double.NaN, user); - - Assert.AreEqual(double.Parse(expected, CultureInfo.InvariantCulture), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); - } - else if (k.StartsWith("integer")) - { - var actual = client.GetValue(keyName, int.MaxValue, user); - - Assert.AreEqual(int.Parse(expected), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); - } - else - { - var actual = client.GetValue(keyName, string.Empty, user); - - Assert.AreEqual(expected, actual, $"keyName: {keyName} | userId: {user?.Identifier}"); - } - } + } [TestMethod] public void ManualPollGetValue() { IConfigCatClient manualPollClient = ConfigCatClientBuilder - .Initialize(APIKEY) + .Initialize(APIKEY) + .WithLogger(consoleLogger) .WithManualPoll() .Create(); @@ -74,6 +41,7 @@ public void AutoPollGetValue() { IConfigCatClient client = ConfigCatClientBuilder .Initialize(APIKEY) + .WithLogger(consoleLogger) .WithAutoPoll() .WithMaxInitWaitTimeSeconds(30) .WithPollIntervalSeconds(600) @@ -87,6 +55,7 @@ public void LazyLoadGetValue() { IConfigCatClient client = ConfigCatClientBuilder .Initialize(APIKEY) + .WithLogger(consoleLogger) .WithLazyLoad() .WithCacheTimeToLiveSeconds(30) .Create(); @@ -99,6 +68,7 @@ public async Task ManualPollGetValueAsync() { IConfigCatClient client = ConfigCatClientBuilder .Initialize(APIKEY) + .WithLogger(consoleLogger) .WithManualPoll() .Create(); @@ -112,6 +82,7 @@ public async Task AutoPollGetValueAsync() { IConfigCatClient client = ConfigCatClientBuilder .Initialize(APIKEY) + .WithLogger(consoleLogger) .WithAutoPoll() .WithMaxInitWaitTimeSeconds(30) .WithPollIntervalSeconds(600) @@ -125,6 +96,7 @@ public async Task LazyLoadGetValueAsync() { IConfigCatClient client = ConfigCatClientBuilder .Initialize(APIKEY) + .WithLogger(consoleLogger) .WithLazyLoad() .WithCacheTimeToLiveSeconds(30) .Create(); @@ -137,6 +109,7 @@ public void GetAllKeys() { IConfigCatClient manualPollClient = ConfigCatClientBuilder .Initialize(APIKEY) + .WithLogger(consoleLogger) .WithManualPoll() .Create(); diff --git a/src/ConfigCat.Client.Tests/BasicConfigEvaluatorTests.cs b/src/ConfigCat.Client.Tests/BasicConfigEvaluatorTests.cs new file mode 100644 index 00000000..8fcd7082 --- /dev/null +++ b/src/ConfigCat.Client.Tests/BasicConfigEvaluatorTests.cs @@ -0,0 +1,51 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace ConfigCat.Client.Tests +{ + [TestClass] + public class BasicConfigEvaluatorTests : ConfigEvaluatorTestsBase + { + protected override string SampleJsonFileName => "sample_v3.json"; + + protected override string MatrixResultFileName => "testmatrix.csv"; + + [TestMethod] + public void GetValue_WithSimpleKey_ShouldReturnCat() + { + string actual = configEvaluator.Evaluate(config, "stringDefaultCat", string.Empty); + + Assert.AreNotEqual(string.Empty, actual); + Assert.AreEqual("Cat", actual); + } + + [TestMethod] + public void GetValue_WithNonExistingKey_ShouldReturnDefaultValue() + { + string actual = configEvaluator.Evaluate(config, "NotExistsKey", "NotExistsValue"); + + Assert.AreEqual("NotExistsValue", actual); + } + + [TestMethod] + public void GetValue_WithEmptyProjectConfig_ShouldReturnDefaultValue() + { + string actual = configEvaluator.Evaluate(ProjectConfig.Empty, "stringDefaultCat", "Default"); + + Assert.AreEqual("Default", actual); + } + + [TestMethod] + public void GetValue_WithUser_ShouldReturnEvaluatedValue() + { + double actual = configEvaluator.Evaluate(config, "doubleDefaultPi", double.NaN, new User("c@configcat.com") + { + Email = "c@configcat.com", + Country = "United Kingdom", + Custom = new Dictionary { { "Custom1", "admin" } } + }); + + Assert.AreEqual(3.1415, actual); + } + } +} diff --git a/src/ConfigCat.Client.Tests/ConfigCat.Client.Tests.csproj b/src/ConfigCat.Client.Tests/ConfigCat.Client.Tests.csproj index 3d755c98..4f36397f 100644 --- a/src/ConfigCat.Client.Tests/ConfigCat.Client.Tests.csproj +++ b/src/ConfigCat.Client.Tests/ConfigCat.Client.Tests.csproj @@ -26,10 +26,31 @@ - + Always - + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + Always diff --git a/src/ConfigCat.Client.Tests/ConfigCatClientTests.cs b/src/ConfigCat.Client.Tests/ConfigCatClientTests.cs index b46b4f46..8fe1d2b0 100644 --- a/src/ConfigCat.Client.Tests/ConfigCatClientTests.cs +++ b/src/ConfigCat.Client.Tests/ConfigCatClientTests.cs @@ -124,12 +124,12 @@ public void CreateAnInstance_WhenManualPollConfigurationIsNull_ShouldThrowArgume [ExpectedException(typeof(ArgumentNullException))] [TestMethod] - public void CreateAnInstance_WhenLoggerFactoryIsNull_ShouldThrowArgumentNullException() + public void CreateAnInstance_WhenLoggerIsNull_ShouldThrowArgumentNullException() { var clientConfiguration = new AutoPollConfiguration { ApiKey = "hsdrTr4sxbHdSgdhHRZds346hdgsS2vfsgf/GsdrTr4sxbHdSgdhHRZds346hdOPsSgvfsgf", - LoggerFactory = null + Logger = null }; new ConfigCatClient(clientConfiguration); diff --git a/src/ConfigCat.Client.Tests/ConfigEvaluatorTests.cs b/src/ConfigCat.Client.Tests/ConfigEvaluatorTestsBase.cs similarity index 53% rename from src/ConfigCat.Client.Tests/ConfigEvaluatorTests.cs rename to src/ConfigCat.Client.Tests/ConfigEvaluatorTestsBase.cs index 64027e69..6f16dea4 100644 --- a/src/ConfigCat.Client.Tests/ConfigEvaluatorTests.cs +++ b/src/ConfigCat.Client.Tests/ConfigEvaluatorTestsBase.cs @@ -9,87 +9,67 @@ namespace ConfigCat.Client.Tests { - [TestClass] - public class ConfigEvaluatorTests + public abstract class ConfigEvaluatorTestsBase { - private static Lazy sampleData = new Lazy(GetSampleV2Json, true); + private readonly ILogger logger = new LoggerWrapper(new ConsoleLogger(LogLevel.Debug)); - private ILogger logger = new NullLogger(); + protected readonly ProjectConfig config; - private readonly ProjectConfig config = new ProjectConfig(sampleData.Value, DateTime.UtcNow, null); + internal readonly IRolloutEvaluator configEvaluator; - private readonly IRolloutEvaluator configEvaluator; + protected abstract string SampleJsonFileName { get; } - public TestContext TestContext { get; set; } + protected abstract string MatrixResultFileName { get; } - public ConfigEvaluatorTests() + public ConfigEvaluatorTestsBase() { this.configEvaluator = new RolloutEvaluator(logger, new ConfigDeserializer(logger)); - } - - [TestMethod] - public void GetValue_WithSimpleKey_ShouldReturnCat() - { - string actual = configEvaluator.Evaluate(config, "stringDefaultCat", string.Empty); - Assert.AreNotEqual(string.Empty, actual); - Assert.AreEqual("Cat", actual); + this.config = new ProjectConfig(this.GetSampleJson(), DateTime.UtcNow, null); } - [TestMethod] - public void GetValue_WithNonExistingKey_ShouldReturnDefaultValue() + protected virtual void AssertValue(string keyName, string expected, User user) { - string actual = configEvaluator.Evaluate(config, "NotExistsKey", "NotExistsValue"); - - Assert.AreEqual("NotExistsValue", actual); - } - - [TestMethod] - public void GetValue_WithEmptyProjectConfig_ShouldReturnDefaultValue() - { - string actual = configEvaluator.Evaluate(ProjectConfig.Empty, "stringDefaultCat", "Default"); - - Assert.AreEqual("Default", actual); - } + var k = keyName.ToLowerInvariant(); - [TestMethod] - public void GetValue_WithUser_ShouldReturnEvaluatedValue() - { - double actual = configEvaluator.Evaluate(config, "doubleDefaultPi", double.NaN, new User("c@configcat.com") + if (k.StartsWith("bool")) { - Email = "c@configcat.com", - Country = "United Kingdom", - Custom = new Dictionary { { "Custom1", "admin" } } - }); - - Assert.AreEqual(3.1415, actual); - } + var actual = configEvaluator.Evaluate(config, keyName, false, user); - [TestMethod] - public void GetValue_WithUserWithIdRules_ShouldReturnEvaluatedValue() - { - var actual = configEvaluator.Evaluate(config, "stringUserWithIdentifier", string.Empty, new User("12345")); + Assert.AreEqual(bool.Parse(expected), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); + } + else if (k.StartsWith("double")) + { + var actual = configEvaluator.Evaluate(config, keyName, double.NaN, user); - Assert.AreEqual("Cat", actual); - } + Assert.AreEqual(double.Parse(expected, CultureInfo.InvariantCulture), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); + } + else if (k.StartsWith("integer")) + { + var actual = configEvaluator.Evaluate(config, keyName, int.MinValue, user); - [TestMethod] - public void GetValue_WithUserWithOtherIdRules_ShouldReturnEvaluatedValue() - { - var actual = configEvaluator.Evaluate(config, "stringUserWithIdentifier", string.Empty, new User("98765")); + Assert.AreEqual(int.Parse(expected), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); + } + else + { + var actual = configEvaluator.Evaluate(config, keyName, string.Empty, user); - Assert.AreEqual("Dog", actual); + Assert.AreEqual(expected, actual, $"keyName: {keyName} | userId: {user?.Identifier}"); + } } - [TestMethod] - public async Task GetValue_MatrixTests() + protected string GetSampleJson() { - await MatrixTest(AssertValue); + using (Stream stream = File.OpenRead("data" + Path.DirectorySeparatorChar + this.SampleJsonFileName)) + using (StreamReader reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } } - public static async Task MatrixTest(Action assertation) + public async Task MatrixTest(Action assertation) { - using (Stream stream = File.OpenRead("testmatrix.csv")) + using (Stream stream = File.OpenRead("data"+ Path.DirectorySeparatorChar + this.MatrixResultFileName)) using (StreamReader reader = new StreamReader(stream)) { var header = await reader.ReadLineAsync(); @@ -127,45 +107,10 @@ public static async Task MatrixTest(Action assertation) } } - private void AssertValue(string keyName, string expected, User user) - { - var k = keyName.ToLowerInvariant(); - - if (k.StartsWith("bool")) - { - var actual = configEvaluator.Evaluate(config, keyName, false, user); - - Assert.AreEqual(bool.Parse(expected), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); - } - else if (k.StartsWith("double")) - { - var actual = configEvaluator.Evaluate(config, keyName, double.NaN, user); - - Assert.AreEqual(double.Parse(expected, CultureInfo.InvariantCulture), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); - } - else if (k.StartsWith("integer")) - { - var actual = configEvaluator.Evaluate(config, keyName, int.MinValue, user); - - Assert.AreEqual(int.Parse(expected), actual, $"keyName: {keyName} | userId: {user?.Identifier}"); - } - else - { - var actual = configEvaluator.Evaluate(config, keyName, string.Empty, user); - - Assert.AreEqual(expected, actual, $"keyName: {keyName} | userId: {user?.Identifier}"); - } - } - - private static string GetSampleV2Json() + [TestMethod] + public async Task GetValue_MatrixTests() { - using (Stream stream = File.OpenRead("sample_v2.json")) - using (StreamReader reader = new StreamReader(stream)) - { - return reader.ReadToEnd(); - } - } + await MatrixTest(AssertValue); + } } - - } diff --git a/src/ConfigCat.Client.Tests/ConfigServiceTests.cs b/src/ConfigCat.Client.Tests/ConfigServiceTests.cs index 86a707bc..14f471b4 100644 --- a/src/ConfigCat.Client.Tests/ConfigServiceTests.cs +++ b/src/ConfigCat.Client.Tests/ConfigServiceTests.cs @@ -14,7 +14,7 @@ public class ConfigServiceTests Mock fetcherMock = new Mock(MockBehavior.Strict); Mock cacheMock = new Mock(MockBehavior.Strict); - Mock logFactoryMock = new Mock(MockBehavior.Strict); + Mock loggerMock = new Mock(MockBehavior.Loose); ProjectConfig cachedPc = new ProjectConfig("CACHED", DateTime.UtcNow.Add(-defaultExpire), "67890"); ProjectConfig fetchedPc = new ProjectConfig("FETCHED", DateTime.UtcNow, "12345"); @@ -24,11 +24,6 @@ public void TestInitialize() { fetcherMock.Reset(); cacheMock.Reset(); - logFactoryMock.Reset(); - - logFactoryMock - .Setup(m => m.GetLogger(It.IsAny())) - .Returns(new NullLogger()); } [TestMethod] @@ -52,7 +47,7 @@ public async Task LazyLoadConfigService_GetConfigAsync_ReturnsExpiredContent_Sho var service = new LazyLoadConfigService( fetcherMock.Object, cacheMock.Object, - logFactoryMock.Object, + loggerMock.Object, defaultExpire); // Act @@ -81,7 +76,7 @@ public async Task LazyLoadConfigService_GetConfigAsync_ReturnsNotExpiredContent_ var service = new LazyLoadConfigService( fetcherMock.Object, cacheMock.Object, - logFactoryMock.Object, + loggerMock.Object, defaultExpire); // Act @@ -123,7 +118,7 @@ public async Task LazyLoadConfigService_RefreshConfigAsync_ShouldNotInvokeCacheG var service = new LazyLoadConfigService( fetcherMock.Object, cacheMock.Object, - logFactoryMock.Object, + loggerMock.Object, defaultExpire); // Act @@ -160,7 +155,7 @@ public async Task AutoPollConfigService_GetConfigAsync_WithoutTimer_ShouldInvoke cacheMock.Object, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1), - logFactoryMock.Object, + loggerMock.Object, false); // Act @@ -198,7 +193,7 @@ public async Task AutoPollConfigService_GetConfigAsync_WithTimer_ShouldInvokeFet cacheMock.Object, TimeSpan.FromMinutes(1), TimeSpan.Zero, - logFactoryMock.Object, + loggerMock.Object, true); // Act @@ -235,7 +230,7 @@ public async Task AutoPollConfigService_RefreshConfigAsync_ShouldNotInvokeCacheG cacheMock.Object, TimeSpan.FromMinutes(1), TimeSpan.Zero, - logFactoryMock.Object, + loggerMock.Object, false); // Act @@ -272,7 +267,7 @@ public async Task AutoPollConfigService_RefreshConfigAsync_ConfigCahged_ShouldRa cacheMock.Object, TimeSpan.FromMinutes(1), TimeSpan.Zero, - logFactoryMock.Object, + loggerMock.Object, false); service.OnConfigurationChanged += (o, s) => { eventChanged++; }; @@ -299,7 +294,7 @@ public async Task ManualPollConfigService_GetConfigAsync_ShouldInvokeCacheGet() var service = new ManualPollConfigService( fetcherMock.Object, cacheMock.Object, - logFactoryMock.Object); + loggerMock.Object); // Act @@ -338,7 +333,7 @@ public async Task ManualPollConfigService_RefreshConfigAsync_ShouldInvokeCacheGe var service = new ManualPollConfigService( fetcherMock.Object, cacheMock.Object, - logFactoryMock.Object); + loggerMock.Object); // Act @@ -369,7 +364,7 @@ public void ConfigService_InvokeDisposeManyTimes_ShouldInvokeFetcherDisposeExact MockBehavior.Loose, configFetcherMock.Object, new InMemoryConfigCache(), - new NullLogger()) + loggerMock.Object) { CallBase = true }; @@ -400,7 +395,7 @@ public void ConfigService_WithNonDisposableConfigFetcher_DisposeShouldWork() MockBehavior.Loose, configFetcherMock.Object, new InMemoryConfigCache(), - new NullLogger()) + new MyCounterLogger()) { CallBase = true }; diff --git a/src/ConfigCat.Client.Tests/HttpConfigFetcherTests.cs b/src/ConfigCat.Client.Tests/HttpConfigFetcherTests.cs index a6ac4a92..0196648a 100644 --- a/src/ConfigCat.Client.Tests/HttpConfigFetcherTests.cs +++ b/src/ConfigCat.Client.Tests/HttpConfigFetcherTests.cs @@ -18,7 +18,7 @@ public async Task HttpConfigFetcher_WithCustomHttpClientHandler_ShouldUsePassedH var myHandler = new MyFakeHttpClientHandler(); - var instance = new HttpConfigFetcher(new Uri("http://example.com"), "1.0", new NullLoggerFactory(), myHandler); + var instance = new HttpConfigFetcher(new Uri("http://example.com"), "1.0", new MyCounterLogger(), myHandler); // Act @@ -36,7 +36,7 @@ public void HttpConfigFetcher_WithCustomHttpClientHandler_HandlersDisposeShouldN var myHandler = new MyFakeHttpClientHandler(); - var instance = new HttpConfigFetcher(new Uri("http://example.com"), "1.0", new NullLoggerFactory(), myHandler); + var instance = new HttpConfigFetcher(new Uri("http://example.com"), "1.0", new MyCounterLogger(), myHandler); // Act diff --git a/src/ConfigCat.Client.Tests/LoggerTests.cs b/src/ConfigCat.Client.Tests/LoggerTests.cs index bfcbb73e..9156eb44 100644 --- a/src/ConfigCat.Client.Tests/LoggerTests.cs +++ b/src/ConfigCat.Client.Tests/LoggerTests.cs @@ -5,111 +5,112 @@ namespace ConfigCat.Client.Tests [TestClass] public class LoggerTests { - [TestMethod] - public void CreateConsoleLogger_ShouldInstanceOfILogger() - { - var factory = new ConsoleLoggerFactory(); - - var instance = factory.GetLogger("dummy"); - - Assert.IsInstanceOfType(instance, typeof(ILogger)); - - instance.Debug(null); - instance.Information(null); - instance.Warning(null); - instance.Error(null); - } - [TestMethod] public void LoggerBase_LoglevelIsDebug_ShouldInvokeErrorOrWarnOrInfoOrDebug() { - var logger = new MyLogger("unittest", LogLevel.Debug); + var l = new MyCounterLogger(LogLevel.Debug); + + var logger = new LoggerWrapper(l); logger.Debug(null); logger.Information(null); logger.Warning(null); logger.Error(null); - Assert.AreEqual(4, logger.LogMessageInvokeCount); + Assert.AreEqual(4, l.LogMessageInvokeCount); } [TestMethod] public void LoggerBase_LoglevelIsInfo_ShouldInvokeOnlyErrorAndWarnAndInfo() { - var logger = new MyLogger("unittest", LogLevel.Info); - + var l = new MyCounterLogger(LogLevel.Info); + + var logger = new LoggerWrapper(l); + logger.Information(null); logger.Warning(null); logger.Error(null); - Assert.AreEqual(3, logger.LogMessageInvokeCount); + Assert.AreEqual(3, l.LogMessageInvokeCount); } [TestMethod] public void LoggerBase_LoglevelIsInfo_ShouldNotInvokeDebug() { - var logger = new MyLogger("unittest", LogLevel.Info); + var l = new MyCounterLogger(LogLevel.Info); + + var logger = new LoggerWrapper(l); logger.Debug(null); - Assert.AreEqual(0, logger.LogMessageInvokeCount); + Assert.AreEqual(0, l.LogMessageInvokeCount); } [TestMethod] public void LoggerBase_LoglevelIsWarn_ShouldInvokeOnlyErrorAndWarnAndInfo() { - var logger = new MyLogger("unittest", LogLevel.Warn); - + var l = new MyCounterLogger(LogLevel.Warning); + + var logger = new LoggerWrapper(l); + logger.Warning(null); logger.Error(null); - Assert.AreEqual(2, logger.LogMessageInvokeCount); + Assert.AreEqual(2, l.LogMessageInvokeCount); } [TestMethod] public void LoggerBase_LoglevelIsWarn_ShouldNotInvokeDebugOrInfo() { - var logger = new MyLogger("unittest", LogLevel.Warn); + var l = new MyCounterLogger(LogLevel.Warning); + + var logger = new LoggerWrapper(l); logger.Debug(null); logger.Information(null); - Assert.AreEqual(0, logger.LogMessageInvokeCount); + Assert.AreEqual(0, l.LogMessageInvokeCount); } [TestMethod] public void LoggerBase_LoglevelIsError_ShouldInvokeOnlyError() { - var logger = new MyLogger("unittest", LogLevel.Error); + var l = new MyCounterLogger(LogLevel.Error); + + var logger = new LoggerWrapper(l); logger.Error(null); - Assert.AreEqual(1, logger.LogMessageInvokeCount); + Assert.AreEqual(1, l.LogMessageInvokeCount); } [TestMethod] public void LoggerBase_LoglevelIsError_ShouldNotInvokeDebugOrInfoOrWarn() { - var logger = new MyLogger("unittest", LogLevel.Error); + var l = new MyCounterLogger(LogLevel.Error); + + var logger = new LoggerWrapper(l); logger.Debug(null); logger.Information(null); logger.Warning(null); - Assert.AreEqual(0, logger.LogMessageInvokeCount); + Assert.AreEqual(0, l.LogMessageInvokeCount); } [TestMethod] public void LoggerBase_LoglevelIsOff_ShouldNotInvokeAnyLogMessage() { - var logger = new MyLogger("unittest", LogLevel.Off); + var l = new MyCounterLogger(LogLevel.Off); + + var logger = new LoggerWrapper(l); logger.Debug(null); logger.Information(null); logger.Warning(null); logger.Error(null); - Assert.AreEqual(0, logger.LogMessageInvokeCount); + Assert.AreEqual(0, l.LogMessageInvokeCount); } } } diff --git a/src/ConfigCat.Client.Tests/MyCounterLogger.cs b/src/ConfigCat.Client.Tests/MyCounterLogger.cs new file mode 100644 index 00000000..73572b76 --- /dev/null +++ b/src/ConfigCat.Client.Tests/MyCounterLogger.cs @@ -0,0 +1,43 @@ +namespace ConfigCat.Client.Tests +{ + internal sealed class MyCounterLogger : ILogger + { + public byte LogMessageInvokeCount = 0; + + public LogLevel LogLevel { get; set; } + + public MyCounterLogger() : this(LogLevel.Debug) { } + + public MyCounterLogger(LogLevel logLevel) + { + this.LogLevel = logLevel; + } + + public void Debug(string message) + { + LogMessage(message); + } + + public void Error(string message) + { + LogMessage(message); + } + + public void Information(string message) + { + LogMessage(message); + } + + public void Warning(string message) + { + LogMessage(message); + } + + private void LogMessage(string message) + { + LogMessageInvokeCount++; + } + } +} + + diff --git a/src/ConfigCat.Client.Tests/MyLogger.cs b/src/ConfigCat.Client.Tests/MyLogger.cs deleted file mode 100644 index 3e8d7010..00000000 --- a/src/ConfigCat.Client.Tests/MyLogger.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace ConfigCat.Client.Tests -{ - internal sealed class MyLogger : LoggerBase - { - public byte LogMessageInvokeCount = 0; - - public MyLogger(string loggerName, LogLevel logLevel) : base(loggerName, logLevel) - { - } - - protected override void LogMessage(string message) - { - LogMessageInvokeCount++; - } - } -} - - diff --git a/src/ConfigCat.Client.Tests/NumericConfigEvaluatorTests.cs b/src/ConfigCat.Client.Tests/NumericConfigEvaluatorTests.cs new file mode 100644 index 00000000..389decb4 --- /dev/null +++ b/src/ConfigCat.Client.Tests/NumericConfigEvaluatorTests.cs @@ -0,0 +1,12 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ConfigCat.Client.Tests +{ + [TestClass] + public class NumericConfigEvaluatorTests : ConfigEvaluatorTestsBase + { + protected override string SampleJsonFileName => "sample_number_v3.json"; + + protected override string MatrixResultFileName => "testmatrix_number.csv"; + } +} diff --git a/src/ConfigCat.Client.Tests/ProjectConfigTests.cs b/src/ConfigCat.Client.Tests/ProjectConfigTests.cs index 5bab8d74..e8fc3fed 100644 --- a/src/ConfigCat.Client.Tests/ProjectConfigTests.cs +++ b/src/ConfigCat.Client.Tests/ProjectConfigTests.cs @@ -62,7 +62,7 @@ public void Equatable_GetHash_ShouldEqual() Assert.IsFalse(set.Add(pc1)); Assert.IsFalse(set.Add(pc2)); Assert.IsFalse(set.Add(pc3)); - Assert.AreEqual(pc1.GetHashCode(), pc2.GetHashCode()); + Assert.AreEqual(pc1.GetHashCode(), pc2.GetHashCode()); } } } diff --git a/src/ConfigCat.Client.Tests/SemanticVersionConfigEvaluatorTests.cs b/src/ConfigCat.Client.Tests/SemanticVersionConfigEvaluatorTests.cs new file mode 100644 index 00000000..1a3826ff --- /dev/null +++ b/src/ConfigCat.Client.Tests/SemanticVersionConfigEvaluatorTests.cs @@ -0,0 +1,12 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ConfigCat.Client.Tests +{ + [TestClass] + public class SemanticVersionConfigEvaluatorTests : ConfigEvaluatorTestsBase + { + protected override string SampleJsonFileName => "sample_semantic_v3.json"; + + protected override string MatrixResultFileName => "testmatrix_semantic.csv"; + } +} diff --git a/src/ConfigCat.Client.Tests/data/sample_number_v2.json b/src/ConfigCat.Client.Tests/data/sample_number_v2.json new file mode 100644 index 00000000..e52cbc4f --- /dev/null +++ b/src/ConfigCat.Client.Tests/data/sample_number_v2.json @@ -0,0 +1,83 @@ +{ + "numberWithPercentage": { + "Value": "Default", + "SettingType": 1, + "RolloutPercentageItems": [ + { + "Order": 0, + "Value": "80%", + "Percentage": 80 + }, + { + "Order": 1, + "Value": "20%", + "Percentage": 20 + } + ], + "RolloutRules": [ + { + "Order": 0, + "ComparisonAttribute": "Custom1", + "Comparator": 10, + "ComparisonValue": "sajt", + "Value": "=sajt" + }, + { + "Order": 1, + "ComparisonAttribute": "Custom1", + "Comparator": 12, + "ComparisonValue": "2.1", + "Value": "<2.1" + }, + { + "Order": 2, + "ComparisonAttribute": "Custom1", + "Comparator": 13, + "ComparisonValue": "2,1", + "Value": "<=2,1" + }, + { + "Order": 3, + "ComparisonAttribute": "Custom1", + "Comparator": 10, + "ComparisonValue": "3.5", + "Value": "=3.5" + }, + { + "Order": 4, + "ComparisonAttribute": "Custom1", + "Comparator": 14, + "ComparisonValue": "5", + "Value": ">5" + }, + { + "Order": 5, + "ComparisonAttribute": "Custom1", + "Comparator": 15, + "ComparisonValue": "5", + "Value": ">=5" + }, + { + "Order": 6, + "ComparisonAttribute": "Custom1", + "Comparator": 11, + "ComparisonValue": "4.2", + "Value": "<>4.2" + } + ] + }, + "number": { + "Value": "Default", + "SettingType": 1, + "RolloutPercentageItems": [], + "RolloutRules": [ + { + "Order": 0, + "ComparisonAttribute": "Custom1", + "Comparator": 11, + "ComparisonValue": "5", + "Value": "<>5" + } + ] + } +} \ No newline at end of file diff --git a/src/ConfigCat.Client.Tests/data/sample_number_v3.json b/src/ConfigCat.Client.Tests/data/sample_number_v3.json new file mode 100644 index 00000000..a6f77509 --- /dev/null +++ b/src/ConfigCat.Client.Tests/data/sample_number_v3.json @@ -0,0 +1 @@ +{"numberWithPercentage":{"v":"Default","t":1,"p":[{"o":0,"v":"80%","p":80},{"o":1,"v":"20%","p":20}],"r":[{"o":0,"a":"Custom1","t":10,"c":"sajt","v":"=sajt"},{"o":1,"a":"Custom1","t":12,"c":"2.1","v":"<2.1"},{"o":2,"a":"Custom1","t":13,"c":"2,1","v":"<=2,1"},{"o":3,"a":"Custom1","t":10,"c":"3.5","v":"=3.5"},{"o":4,"a":"Custom1","t":14,"c":"5","v":">5"},{"o":5,"a":"Custom1","t":15,"c":"5","v":">=5"},{"o":6,"a":"Custom1","t":11,"c":"4.2","v":"<>4.2"}]},"number":{"v":"Default","t":1,"p":[],"r":[{"o":0,"a":"Custom1","t":11,"c":"5","v":"<>5"}]}} \ No newline at end of file diff --git a/src/ConfigCat.Client.Tests/data/sample_semantic_v2.json b/src/ConfigCat.Client.Tests/data/sample_semantic_v2.json new file mode 100644 index 00000000..66f65782 --- /dev/null +++ b/src/ConfigCat.Client.Tests/data/sample_semantic_v2.json @@ -0,0 +1,217 @@ +{ + "isOneOf": { + "Value": "Default", + "SettingType": 1, + "RolloutPercentageItems": [], + "RolloutRules": [ + { + "Order": 0, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "1.0.0, 2", + "Value": "Is one of (1.0.0, 2)" + }, + { + "Order": 1, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "1.0.0", + "Value": "Is one of (1.0.0)" + }, + { + "Order": 2, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": " , 2.0.1, 2.0.2, ", + "Value": "Is one of ( , 2.0.1, 2.0.2, )" + }, + { + "Order": 3, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "3......", + "Value": "Is one of (3......)" + }, + { + "Order": 4, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "3....", + "Value": "Is one of (3...)" + }, + { + "Order": 5, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "3..0", + "Value": "Is one of (3..0)" + }, + { + "Order": 6, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "3.0", + "Value": "Is one of (3.0)" + }, + { + "Order": 7, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "3.0.", + "Value": "Is one of (3.0.)" + }, + { + "Order": 8, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "3.0.0", + "Value": "Is one of (3.0.0)" + } + ] + }, + "isOneOfWithPercentage": { + "Value": "Default", + "SettingType": 1, + "RolloutPercentageItems": [ + { + "Order": 0, + "Value": "20%", + "Percentage": 20 + }, + { + "Order": 1, + "Value": "80%", + "Percentage": 80 + } + ], + "RolloutRules": [ + { + "Order": 0, + "ComparisonAttribute": "Custom1", + "Comparator": 4, + "ComparisonValue": "1.0.0", + "Value": "is one of (1.0.0)" + } + ] + }, + "isNotOneOf": { + "Value": "Default", + "SettingType": 1, + "RolloutPercentageItems": [], + "RolloutRules": [ + { + "Order": 0, + "ComparisonAttribute": "Custom1", + "Comparator": 5, + "ComparisonValue": "1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, ", + "Value": "Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, )" + }, + { + "Order": 1, + "ComparisonAttribute": "Custom1", + "Comparator": 5, + "ComparisonValue": "1.0.0, 3.0.1", + "Value": "Is not one of (1.0.0, 3.0.1)" + } + ] + }, + "isNotOneOfWithPercentage": { + "Value": "Default", + "SettingType": 1, + "RolloutPercentageItems": [ + { + "Order": 0, + "Value": "20%", + "Percentage": 20 + }, + { + "Order": 1, + "Value": "80%", + "Percentage": 80 + } + ], + "RolloutRules": [ + { + "Order": 0, + "ComparisonAttribute": "Custom1", + "Comparator": 5, + "ComparisonValue": "1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, ", + "Value": "Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, )" + }, + { + "Order": 1, + "ComparisonAttribute": "Custom1", + "Comparator": 5, + "ComparisonValue": "1.0.0, 3.0.1", + "Value": "Is not one of (1.0.0, 3.0.1)" + } + ] + }, + "lessThanWithPercentage": { + "Value": "Default", + "SettingType": 1, + "RolloutPercentageItems": [ + { + "Order": 0, + "Value": "20%", + "Percentage": 20 + }, + { + "Order": 1, + "Value": "80%", + "Percentage": 80 + } + ], + "RolloutRules": [ + { + "Order": 0, + "ComparisonAttribute": "Custom1", + "Comparator": 6, + "ComparisonValue": " 1.0.0 ", + "Value": "< 1.0.0" + } + ] + }, + "relations": { + "Value": "Default", + "SettingType": 1, + "RolloutPercentageItems": [], + "RolloutRules": [ + { + "Order": 0, + "ComparisonAttribute": "Custom1", + "Comparator": 6, + "ComparisonValue": "1.0.0,", + "Value": "<1.0.0," + }, + { + "Order": 1, + "ComparisonAttribute": "Custom1", + "Comparator": 6, + "ComparisonValue": "1.0.0", + "Value": "< 1.0.0" + }, + { + "Order": 2, + "ComparisonAttribute": "Custom1", + "Comparator": 7, + "ComparisonValue": "1.0.0", + "Value": "<=1.0.0" + }, + { + "Order": 3, + "ComparisonAttribute": "Custom1", + "Comparator": 8, + "ComparisonValue": "2.0.0", + "Value": ">2.0.0" + }, + { + "Order": 4, + "ComparisonAttribute": "Custom1", + "Comparator": 9, + "ComparisonValue": "2.0.0", + "Value": ">=2.0.0" + } + ] + } +} \ No newline at end of file diff --git a/src/ConfigCat.Client.Tests/data/sample_semantic_v3.json b/src/ConfigCat.Client.Tests/data/sample_semantic_v3.json new file mode 100644 index 00000000..e211b833 --- /dev/null +++ b/src/ConfigCat.Client.Tests/data/sample_semantic_v3.json @@ -0,0 +1 @@ +{"isOneOf":{"v":"Default","t":1,"p":[],"r":[{"o":0,"a":"Custom1","t":4,"c":"1.0.0, 2","v":"Is one of (1.0.0, 2)"},{"o":1,"a":"Custom1","t":4,"c":"1.0.0","v":"Is one of (1.0.0)"},{"o":2,"a":"Custom1","t":4,"c":" , 2.0.1, 2.0.2, ","v":"Is one of ( , 2.0.1, 2.0.2, )"},{"o":3,"a":"Custom1","t":4,"c":"3......","v":"Is one of (3......)"},{"o":4,"a":"Custom1","t":4,"c":"3....","v":"Is one of (3...)"},{"o":5,"a":"Custom1","t":4,"c":"3..0","v":"Is one of (3..0)"},{"o":6,"a":"Custom1","t":4,"c":"3.0","v":"Is one of (3.0)"},{"o":7,"a":"Custom1","t":4,"c":"3.0.","v":"Is one of (3.0.)"},{"o":8,"a":"Custom1","t":4,"c":"3.0.0","v":"Is one of (3.0.0)"}]},"isOneOfWithPercentage":{"v":"Default","t":1,"p":[{"o":0,"v":"20%","p":20},{"o":1,"v":"80%","p":80}],"r":[{"o":0,"a":"Custom1","t":4,"c":"1.0.0","v":"is one of (1.0.0)"}]},"isNotOneOf":{"v":"Default","t":1,"p":[],"r":[{"o":0,"a":"Custom1","t":5,"c":"1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, ","v":"Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, )"},{"o":1,"a":"Custom1","t":5,"c":"1.0.0, 3.0.1","v":"Is not one of (1.0.0, 3.0.1)"}]},"isNotOneOfWithPercentage":{"v":"Default","t":1,"p":[{"o":0,"v":"20%","p":20},{"o":1,"v":"80%","p":80}],"r":[{"o":0,"a":"Custom1","t":5,"c":"1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, ","v":"Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, )"},{"o":1,"a":"Custom1","t":5,"c":"1.0.0, 3.0.1","v":"Is not one of (1.0.0, 3.0.1)"}]},"lessThanWithPercentage":{"v":"Default","t":1,"p":[{"o":0,"v":"20%","p":20},{"o":1,"v":"80%","p":80}],"r":[{"o":0,"a":"Custom1","t":6,"c":" 1.0.0 ","v":"< 1.0.0"}]},"relations":{"v":"Default","t":1,"p":[],"r":[{"o":0,"a":"Custom1","t":6,"c":"1.0.0,","v":"<1.0.0,"},{"o":1,"a":"Custom1","t":6,"c":"1.0.0","v":"< 1.0.0"},{"o":2,"a":"Custom1","t":7,"c":"1.0.0","v":"<=1.0.0"},{"o":3,"a":"Custom1","t":8,"c":"2.0.0","v":">2.0.0"},{"o":4,"a":"Custom1","t":9,"c":"2.0.0","v":">=2.0.0"}]}} \ No newline at end of file diff --git a/src/ConfigCat.Client.Tests/sample_v2.json b/src/ConfigCat.Client.Tests/data/sample_v2.json similarity index 100% rename from src/ConfigCat.Client.Tests/sample_v2.json rename to src/ConfigCat.Client.Tests/data/sample_v2.json diff --git a/src/ConfigCat.Client.Tests/data/sample_v3.json b/src/ConfigCat.Client.Tests/data/sample_v3.json new file mode 100644 index 00000000..165169ed --- /dev/null +++ b/src/ConfigCat.Client.Tests/data/sample_v3.json @@ -0,0 +1,332 @@ +{ + "stringDefaultCat": { + "v": "Cat", + "t": 1, + "p": [], + "r": [] + }, + "stringIsInDogDefaultCat": { + "v": "Cat", + "t": 1, + "p": [], + "r": [ + { + "o": 0, + "a": "Email", + "t": 0, + "c": "a@configcat.com, b@configcat.com", + "v": "Dog" + }, + { + "o": 1, + "a": "Custom1", + "t": 0, + "c": "admin", + "v": "Dog" + } + ] + }, + "stringIsNotInDogDefaultCat": { + "v": "Cat", + "t": 1, + "p": [], + "r": [ + { + "o": 0, + "a": "Email", + "t": 1, + "c": "a@configcat.com,b@configcat.com", + "v": "Dog" + } + ] + }, + "stringContainsDogDefaultCat": { + "v": "Cat", + "t": 1, + "p": [], + "r": [ + { + "o": 0, + "a": "Email", + "t": 2, + "c": "@configcat.com", + "v": "Dog" + } + ] + }, + "stringNotContainsDogDefaultCat": { + "v": "Cat", + "t": 1, + "p": [], + "r": [ + { + "o": 0, + "a": "Email", + "t": 3, + "c": "@configcat.com", + "v": "Dog" + } + ] + }, + "string25Cat25Dog25Falcon25Horse": { + "v": "Chicken", + "t": 1, + "p": [ + { + "o": 0, + "v": "Cat", + "p": 25 + }, + { + "o": 1, + "v": "Dog", + "p": 25 + }, + { + "o": 2, + "v": "Falcon", + "p": 25 + }, + { + "o": 3, + "v": "Horse", + "p": 25 + } + ], + "r": [] + }, + "string75Cat0Dog25Falcon0Horse": { + "v": "Chicken", + "t": 1, + "p": [ + { + "o": 0, + "v": "Cat", + "p": 75 + }, + { + "o": 1, + "v": "Dog", + "p": 0 + }, + { + "o": 2, + "v": "Falcon", + "p": 25 + }, + { + "o": 3, + "v": "Horse", + "p": 0 + } + ], + "r": [] + }, + "string25Cat25Dog25Falcon25HorseAdvancedRules": { + "v": "Chicken", + "t": 1, + "p": [ + { + "o": 0, + "v": "Cat", + "p": 25 + }, + { + "o": 1, + "v": "Dog", + "p": 25 + }, + { + "o": 2, + "v": "Falcon", + "p": 25 + }, + { + "o": 3, + "v": "Horse", + "p": 25 + } + ], + "r": [ + { + "o": 0, + "a": "Country", + "t": 0, + "c": "Hungary, United Kingdom", + "v": "Dolphin" + }, + { + "o": 1, + "a": "Custom1", + "t": 2, + "c": "admi", + "v": "Lion" + }, + { + "o": 2, + "a": "Email", + "t": 2, + "c": "@configcat.com", + "v": "Kitten" + } + ] + }, + "boolDefaultTrue": { + "v": true, + "t": 0, + "p": [], + "r": [] + }, + "boolDefaultFalse": { + "v": false, + "t": 0, + "p": [], + "r": [] + }, + "bool30TrueAdvancedRules": { + "v": true, + "t": 0, + "p": [ + { + "o": 0, + "v": true, + "p": 30 + }, + { + "o": 1, + "v": false, + "p": 70 + } + ], + "r": [ + { + "o": 0, + "a": "Email", + "t": 0, + "c": "a@configcat.com, b@configcat.com", + "v": false + }, + { + "o": 1, + "a": "Country", + "t": 2, + "c": "United", + "v": false + } + ] + }, + "integer25One25Two25Three25FourAdvancedRules": { + "v": -1, + "t": 2, + "p": [ + { + "o": 0, + "v": 1, + "p": 25 + }, + { + "o": 1, + "v": 2, + "p": 25 + }, + { + "o": 2, + "v": 3, + "p": 25 + }, + { + "o": 3, + "v": 4, + "p": 25 + } + ], + "r": [ + { + "o": 0, + "a": "Email", + "t": 2, + "c": "@configcat.com", + "v": 5 + } + ] + }, + "integerDefaultOne": { + "v": 1, + "t": 2, + "p": [], + "r": [] + }, + "doubleDefaultPi": { + "v": 3.1415, + "t": 3, + "p": [], + "r": [] + }, + "double25Pi25E25Gr25Zero": { + "v": -1.0, + "t": 3, + "p": [ + { + "o": 0, + "v": 3.1415, + "p": 25 + }, + { + "o": 1, + "v": 2.7182, + "p": 25 + }, + { + "o": 2, + "v": 1.61803, + "p": 25 + }, + { + "o": 3, + "v": 0.0, + "p": 25 + } + ], + "r": [ + { + "o": 0, + "a": "Email", + "t": 2, + "c": "@configcat.com", + "v": 5.561 + } + ] + }, + "keySampleText": { + "v": "Cat", + "t": 1, + "p": [ + { + "o": 0, + "v": "Falcon", + "p": 50 + }, + { + "o": 1, + "v": "Horse", + "p": 50 + } + ], + "r": [ + { + "o": 0, + "a": "Country", + "t": 0, + "c": "Hungary,Bahamas", + "v": "Dog" + }, + { + "o": 1, + "a": "SubscriptionType", + "t": 0, + "c": "unlimited", + "v": "Lion" + } + ] + } +} \ No newline at end of file diff --git a/src/ConfigCat.Client.Tests/testmatrix.csv b/src/ConfigCat.Client.Tests/data/testmatrix.csv similarity index 100% rename from src/ConfigCat.Client.Tests/testmatrix.csv rename to src/ConfigCat.Client.Tests/data/testmatrix.csv diff --git a/src/ConfigCat.Client.Tests/data/testmatrix_number.csv b/src/ConfigCat.Client.Tests/data/testmatrix_number.csv new file mode 100644 index 00000000..56a53508 --- /dev/null +++ b/src/ConfigCat.Client.Tests/data/testmatrix_number.csv @@ -0,0 +1,26 @@ +Identifier;Email;Country;Custom1;numberWithPercentage;number +##null##;;;;Default;Default +id1;;;0;<2.1;<>5 +id1;;;0.0;<2.1;<>5 +id1;;;0,0;<2.1;<>5 +id1;;;0.2;<2.1;<>5 +id2;;;0,2;<2.1;<>5 +id3;;;1;<2.1;<>5 +id4;;;1.0;<2.1;<>5 +id5;;;1,0;<2.1;<>5 +id6;;;1.5;<2.1;<>5 +id7;;;1,5;<2.1;<>5 +id8;;;2.1;<=2,1;<>5 +id9;;;2,1;<=2,1;<>5 +id10;;;3.50;=3.5;<>5 +id11;;;3,50;=3.5;<>5 +id12;;;5;>=5;Default +id13;;;5.0;>=5;Default +id14;;;5,0;>=5;Default +id13;;;5.76;>5;<>5 +id14;;;5,76;>5;<>5 +id15;;;4;<>4.2;<>5 +id16;;;4.0;<>4.2;<>5 +id17;;;4,0;<>4.2;<>5 +id18;;;4.2;80%;<>5 +id19;;;4,2;20%;<>5 diff --git a/src/ConfigCat.Client.Tests/data/testmatrix_semantic.csv b/src/ConfigCat.Client.Tests/data/testmatrix_semantic.csv new file mode 100644 index 00000000..a5a70465 --- /dev/null +++ b/src/ConfigCat.Client.Tests/data/testmatrix_semantic.csv @@ -0,0 +1,36 @@ +Identifier;Email;Country;Custom1;isOneOf;isOneOfWithPercentage;isNotOneOf;isNotOneOfWithPercentage;lessThanWithPercentage;relations +##null##;;;;Default;Default;Default;Default;Default;Default +id1;;;0.0.0;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );< 1.0.0;< 1.0.0 +id1;;;0.1.0;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );< 1.0.0;< 1.0.0 +id1;;;0.2.1;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );< 1.0.0;< 1.0.0 +id1;;;1;Default;80%;Default;80%;20%;Default +id2;;;1.0;Default;80%;Default;80%;80%;Default +id3;;;1.0.0;Is one of (1.0.0);is one of (1.0.0);Default;80%;80%;<=1.0.0 +id4;;;1.0.0.0;Default;80%;Default;20%;20%;Default +id5;;;1.0.0.0.0;Default;80%;Default;80%;80%;Default +id6;;;1.0.1;Default;80%;Is not one of (1.0.0, 3.0.1);Is not one of (1.0.0, 3.0.1);80%;Default +id7;;;1.0.11;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );20%;Default +id8;;;1.0.111;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id9;;;1.0.2;Default;20%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id10;;;1.0.3;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id11;;;1.0.4;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id12;;;1.0.5;Default;20%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id13;;;1.1.0;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id14;;;1.1.1;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id15;;;1.1.2;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id16;;;1.1.3;Default;20%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );20%;Default +id17;;;1.1.4;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );20%;Default +id18;;;1.1.5;Default;20%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id19;;;1.9.0;Default;20%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;Default +id20;;;1.9.99;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );20%;Default +id21;;;2.0.0;Default;80%;Is not one of (1.0.0, 3.0.1);Is not one of (1.0.0, 3.0.1);20%;>=2.0.0 +id22;;;2.0.1;Is one of ( , 2.0.1, 2.0.2, );80%;Is not one of (1.0.0, 3.0.1);Is not one of (1.0.0, 3.0.1);80%;>2.0.0 +id23;;;2.0.11;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );20%;>2.0.0 +id24;;;2.0.2;Is one of ( , 2.0.1, 2.0.2, );80%;Is not one of (1.0.0, 3.0.1);Is not one of (1.0.0, 3.0.1);80%;>2.0.0 +id25;;;2.0.3;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;>2.0.0 +id26;;;3.0.0;Is one of (3.0.0);80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;>2.0.0 +id27;;;3.0.1;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );20%;>2.0.0 +id28;;;3.1.0;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;>2.0.0 +id28;;;3.1.1;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;>2.0.0 +id29;;;5.0.0;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );80%;>2.0.0 +id30;;;5.99.999;Default;80%;Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );Is not one of (1.0.0, 1.0.1, 2.0.0 , 2.0.1, 2.0.2, );20%;>2.0.0 diff --git a/src/ConfigCatClient/ConfigCatClient.cs b/src/ConfigCatClient/ConfigCatClient.cs index a7a14b2b..7cff06d1 100644 --- a/src/ConfigCatClient/ConfigCatClient.cs +++ b/src/ConfigCatClient/ConfigCatClient.cs @@ -13,9 +13,9 @@ namespace ConfigCat.Client /// public class ConfigCatClient : IConfigCatClient { - private ILogger log; + private readonly ILogger log; - private IRolloutEvaluator configEvaluator; + private readonly IRolloutEvaluator configEvaluator; private readonly IConfigService configService; @@ -23,6 +23,19 @@ public class ConfigCatClient : IConfigCatClient private static readonly string version = typeof(ConfigCatClient).GetTypeInfo().Assembly.GetCustomAttribute().InformationalVersion; + /// + public LogLevel LogLevel + { + get + { + return log.LogLevel; + } + set + { + log.LogLevel = value; + } + } + /// /// Create an instance of ConfigCatClient and setup AutoPoll mode /// @@ -42,11 +55,11 @@ public ConfigCatClient(AutoPollConfiguration configuration) : this((ConfigurationBase)configuration) { var configService = new AutoPollConfigService( - new HttpConfigFetcher(configuration.CreateUrl(), "a-" + version, configuration.LoggerFactory, configuration.HttpClientHandler), + new HttpConfigFetcher(configuration.CreateUrl(), "a-" + version, configuration.Logger, configuration.HttpClientHandler), configuration.ConfigCache ?? new InMemoryConfigCache(), TimeSpan.FromSeconds(configuration.PollIntervalSeconds), TimeSpan.FromSeconds(configuration.MaxInitWaitTimeSeconds), - configuration.LoggerFactory); + configuration.Logger); configService.OnConfigurationChanged += configuration.RaiseOnConfigurationChanged; @@ -63,9 +76,9 @@ public ConfigCatClient(LazyLoadConfiguration configuration) : this((ConfigurationBase)configuration) { var configService = new LazyLoadConfigService( - new HttpConfigFetcher(configuration.CreateUrl(), "l-" + version, configuration.LoggerFactory, configuration.HttpClientHandler), + new HttpConfigFetcher(configuration.CreateUrl(), "l-" + version, configuration.Logger, configuration.HttpClientHandler), configuration.ConfigCache ?? new InMemoryConfigCache(), - configuration.LoggerFactory, + configuration.Logger, TimeSpan.FromSeconds(configuration.CacheTimeToLiveSeconds)); this.configService = configService; @@ -81,9 +94,9 @@ public ConfigCatClient(ManualPollConfiguration configuration) : this((ConfigurationBase)configuration) { var configService = new ManualPollConfigService( - new HttpConfigFetcher(configuration.CreateUrl(), "m-" + version, configuration.LoggerFactory, configuration.HttpClientHandler), + new HttpConfigFetcher(configuration.CreateUrl(), "m-" + version, configuration.Logger, configuration.HttpClientHandler), configuration.ConfigCache ?? new InMemoryConfigCache(), - configuration.LoggerFactory); + configuration.Logger); this.configService = configService; } @@ -97,7 +110,7 @@ private ConfigCatClient(ConfigurationBase configuration) configuration.Validate(); - this.log = configuration.LoggerFactory.GetLogger(nameof(ConfigCatClient)); + this.log = configuration.Logger; this.configDeserializer = new ConfigDeserializer(this.log); this.configEvaluator = new RolloutEvaluator(this.log, this.configDeserializer); } diff --git a/src/ConfigCatClient/ConfigCatClient.csproj b/src/ConfigCatClient/ConfigCatClient.csproj index 5304f86c..9bead90b 100644 --- a/src/ConfigCatClient/ConfigCatClient.csproj +++ b/src/ConfigCatClient/ConfigCatClient.csproj @@ -66,7 +66,8 @@ Works with .NET, .NET Core, .NET Standard - + + diff --git a/src/ConfigCatClient/ConfigCatClientBuilder.cs b/src/ConfigCatClient/ConfigCatClientBuilder.cs index 1947c73d..6b6fccc8 100644 --- a/src/ConfigCatClient/ConfigCatClientBuilder.cs +++ b/src/ConfigCatClient/ConfigCatClientBuilder.cs @@ -6,6 +6,7 @@ public class ConfigCatClientBuilder { internal string ApiKey { get; private set; } + internal ILogger Logger { get; private set; } = new ConsoleLogger(); /// /// Create a instance with @@ -42,5 +43,17 @@ public LazyLoadConfigurationBuilder WithLazyLoad() { return new LazyLoadConfigurationBuilder(this); } + + /// + /// Set Logger instance + /// + /// Implementation of ILogger + /// + public ConfigCatClientBuilder WithLogger(ILogger logger) + { + this.Logger = logger; + + return this; + } } } \ No newline at end of file diff --git a/src/ConfigCatClient/ConfigService/AutoPollConfigService.cs b/src/ConfigCatClient/ConfigService/AutoPollConfigService.cs index 3b99120d..8308600f 100644 --- a/src/ConfigCatClient/ConfigService/AutoPollConfigService.cs +++ b/src/ConfigCatClient/ConfigService/AutoPollConfigService.cs @@ -17,7 +17,7 @@ internal AutoPollConfigService( IConfigCache configCache, TimeSpan pollingInterval, TimeSpan maxInitWaitTime, - ILoggerFactory loggerFactory) : this(configFetcher, configCache, pollingInterval, maxInitWaitTime, loggerFactory, true) + ILogger logger) : this(configFetcher, configCache, pollingInterval, maxInitWaitTime, logger, true) { } @@ -28,15 +28,15 @@ internal AutoPollConfigService( /// /// /// - /// + /// /// internal AutoPollConfigService( IConfigFetcher configFetcher, IConfigCache configCache, TimeSpan pollingInterval, TimeSpan maxInitWaitTime, - ILoggerFactory loggerFactory, - bool startTimer) : base(configFetcher, configCache, loggerFactory.GetLogger(nameof(AutoPollConfigService))) + ILogger logger, + bool startTimer) : base(configFetcher, configCache, logger) { if (startTimer) { diff --git a/src/ConfigCatClient/ConfigService/LazyLoadConfigService.cs b/src/ConfigCatClient/ConfigService/LazyLoadConfigService.cs index f1c1fbe0..146b4d5f 100644 --- a/src/ConfigCatClient/ConfigService/LazyLoadConfigService.cs +++ b/src/ConfigCatClient/ConfigService/LazyLoadConfigService.cs @@ -7,8 +7,8 @@ internal sealed class LazyLoadConfigService : ConfigServiceBase, IConfigService { private readonly TimeSpan cacheTimeToLive; - internal LazyLoadConfigService(IConfigFetcher configFetcher, IConfigCache configCache, ILoggerFactory loggerFactory, TimeSpan cacheTimeToLive) - : base(configFetcher, configCache, loggerFactory.GetLogger(nameof(LazyLoadConfigService))) + internal LazyLoadConfigService(IConfigFetcher configFetcher, IConfigCache configCache, ILogger logger, TimeSpan cacheTimeToLive) + : base(configFetcher, configCache, logger) { this.cacheTimeToLive = cacheTimeToLive; } diff --git a/src/ConfigCatClient/ConfigService/ManualPollConfigService.cs b/src/ConfigCatClient/ConfigService/ManualPollConfigService.cs index 2c39e797..675d9d21 100644 --- a/src/ConfigCatClient/ConfigService/ManualPollConfigService.cs +++ b/src/ConfigCatClient/ConfigService/ManualPollConfigService.cs @@ -4,8 +4,8 @@ namespace ConfigCat.Client.ConfigService { internal sealed class ManualPollConfigService : ConfigServiceBase, IConfigService { - internal ManualPollConfigService(IConfigFetcher configFetcher, IConfigCache configCache, ILoggerFactory loggerFactory) - : base(configFetcher, configCache, loggerFactory.GetLogger(nameof(ManualPollConfigService))) { } + internal ManualPollConfigService(IConfigFetcher configFetcher, IConfigCache configCache, ILogger logger) + : base(configFetcher, configCache, logger) { } public Task GetConfigAsync() { diff --git a/src/ConfigCatClient/Configuration/ConfigurationBase.cs b/src/ConfigCatClient/Configuration/ConfigurationBase.cs index 23797126..82c11951 100644 --- a/src/ConfigCatClient/Configuration/ConfigurationBase.cs +++ b/src/ConfigCatClient/Configuration/ConfigurationBase.cs @@ -8,10 +8,22 @@ namespace ConfigCat.Client /// public abstract class ConfigurationBase { + private ILogger _logger; + /// - /// Factory method of ILogger + /// Logger instance /// - public ILoggerFactory LoggerFactory { get; set; } = new NullLoggerFactory(); + public ILogger Logger + { + get + { + return this._logger ?? new LoggerWrapper(new ConsoleLogger()); + } + set + { + this._logger = new LoggerWrapper(value ?? throw new ArgumentNullException(nameof(Logger))); + } + } /// /// Api key to get your configuration @@ -26,7 +38,7 @@ public abstract class ConfigurationBase /// /// HttpClientHandler to provide network credentials and proxy settings /// - public HttpClientHandler HttpClientHandler { get; set; } + public HttpClientHandler HttpClientHandler { get; set; } /// /// You can set a BaseUrl if you want to use a proxy server between your application and ConfigCat @@ -40,15 +52,15 @@ internal virtual void Validate() throw new ArgumentException("Invalid api key value.", nameof(this.ApiKey)); } - if (this.LoggerFactory == null) + if (this.Logger == null) { - throw new ArgumentNullException(nameof(this.LoggerFactory)); + throw new ArgumentNullException(nameof(this.Logger)); } } internal Uri CreateUrl() { - return new Uri(BaseUrl, "configuration-files/" + this.ApiKey + "/config_v2.json"); + return new Uri(BaseUrl, "configuration-files/" + this.ApiKey + "/config_v3.json"); } } } \ No newline at end of file diff --git a/src/ConfigCatClient/Configuration/ConfigurationBuilderBase.cs b/src/ConfigCatClient/Configuration/ConfigurationBuilderBase.cs index ef13328d..6baeb131 100644 --- a/src/ConfigCatClient/Configuration/ConfigurationBuilderBase.cs +++ b/src/ConfigCatClient/Configuration/ConfigurationBuilderBase.cs @@ -12,7 +12,8 @@ internal ConfigurationBuilderBase(ConfigCatClientBuilder clientBuilder) { this.configuration = new T { - ApiKey = clientBuilder.ApiKey + ApiKey = clientBuilder.ApiKey, + Logger = clientBuilder.Logger }; } } diff --git a/src/ConfigCatClient/Evaluate/EvaluateLogger.cs b/src/ConfigCatClient/Evaluate/EvaluateLogger.cs new file mode 100644 index 00000000..da231fcb --- /dev/null +++ b/src/ConfigCatClient/Evaluate/EvaluateLogger.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.Text; + +namespace ConfigCat.Client.Evaluate +{ + internal sealed class EvaluateLogger + { + public User User { get; set; } + + public T ReturnValue { get; set; } + + public string KeyName { get; set; } + + public ICollection Operations { get; private set; } = new List(); + + public void Log(string message) + { + this.Operations.Add(message); + } + + public override string ToString() + { + var result = new StringBuilder(); + + result.AppendLine($" Evaluate '{KeyName}'"); + + result.AppendLine($" User object: {Newtonsoft.Json.JsonConvert.SerializeObject(this.User)}"); + + foreach (var o in this.Operations) + { + result.AppendLine(" " + o); + } + + result.AppendLine($" Returning: {this.ReturnValue}"); + + return result.ToString(); + } + + public static string FormatComparator(ComparatorEnum comparator) + { + switch (comparator) + { + case ComparatorEnum.In: + case ComparatorEnum.SemVerIn: + return "IS ONE OF"; + case ComparatorEnum.NotIn: + case ComparatorEnum.SemVerNotIn: + return "IS NOT ONE OF"; + case ComparatorEnum.Contains: + return "CONTAINS"; + case ComparatorEnum.NotContains: + return "DOES NOT CONTAIN"; + case ComparatorEnum.SemVerLessThan: + case ComparatorEnum.NumberLessThan: + return "<"; + case ComparatorEnum.SemVerLessThanEqual: + case ComparatorEnum.NumberLessThanEqual: + return "<="; + case ComparatorEnum.SemVerGreaterThan: + case ComparatorEnum.NumberGreaterThan: + return ">"; + case ComparatorEnum.SemVerGreaterThanEqual: + case ComparatorEnum.NumberGreaterThanEqual: + return ">="; + case ComparatorEnum.NumberEqual: + return "="; + case ComparatorEnum.NumberNotEqual: + return "!="; + default: + return comparator.ToString(); + } + } + } +} diff --git a/src/ConfigCatClient/Evaluate/RolloutEvaluator.cs b/src/ConfigCatClient/Evaluate/RolloutEvaluator.cs index a390e90c..6d52a405 100644 --- a/src/ConfigCatClient/Evaluate/RolloutEvaluator.cs +++ b/src/ConfigCatClient/Evaluate/RolloutEvaluator.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json.Linq; +using Semver; using System; using System.Collections.Generic; using System.Globalization; @@ -30,33 +31,65 @@ public T Evaluate(ProjectConfig projectConfig, string key, T defaultValue, Us if (!settings.TryGetValue(key, out var setting)) { - this.log.Warning($"Unknown key: '{key}'"); + var keys = string.Join(",", settings.Keys.Select(s => $"'{s}'").ToArray()); + + this.log.Error($"Evaluating '{key}' failed. Returning default value: '{defaultValue}'. Here are the available keys: {keys}."); return defaultValue; } - if (user != null) + var evaluateLog = new EvaluateLogger { - // evaluate rules + ReturnValue = defaultValue, + User = user, + KeyName = key + }; + try + { T result; - if (TryEvaluateRules(setting.RolloutRules, user, out result)) + if (user != null) { - return result; - } + // evaluate rules + + if (TryEvaluateRules(setting.RolloutRules, user, evaluateLog, out result)) + { + evaluateLog.ReturnValue = result; + return result; + } - // evaluate variations + // evaluate variations - if (TryEvaluateVariations(setting.RolloutPercentageItems, key, user, out result)) + if (TryEvaluateVariations(setting.RolloutPercentageItems, key, user, out result)) + { + evaluateLog.Log("evaluate % option => user targeted"); + evaluateLog.ReturnValue = result; + + return result; + } + else + { + evaluateLog.Log("evaluate % option => user not targeted"); + } + } + else if (user == null && (setting.RolloutRules.Any() || setting.RolloutPercentageItems.Any())) { - return result; + this.log.Warning($"Evaluating '{key}'. UserObject missing! You should pass a UserObject to GetValue() or GetValueAsync(), in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object"); } - } - // regular evaluate + // regular evaluate + + result = new JValue(setting.Value).Value(); + + evaluateLog.ReturnValue = result; - return new JValue(setting.Value).Value(); + return result; + } + finally + { + this.log.Information(evaluateLog.ToString()); + } } private bool TryEvaluateVariations(ICollection rolloutPercentageItems, string key, User user, out T result) @@ -79,7 +112,7 @@ private bool TryEvaluateVariations(ICollection rollout if (hashScale < bucket) { - result = new JValue(variation.RawValue).Value(); + result = new JValue(variation.RawValue).Value(); return true; } @@ -89,7 +122,7 @@ private bool TryEvaluateVariations(ICollection rollout return false; } - private static bool TryEvaluateRules(ICollection rules, User user, out T result) + private static bool TryEvaluateRules(ICollection rules, User user, EvaluateLogger logger, out T result) { result = default(T); @@ -110,6 +143,8 @@ private static bool TryEvaluateRules(ICollection rules, User use continue; } + string l = $"evaluate rule: '{comparisonAttributeValue}' {EvaluateLogger.FormatComparator(rule.Comparator)} '{rule.ComparisonValue}' => "; + switch (rule.Comparator) { case ComparatorEnum.In: @@ -119,9 +154,13 @@ private static bool TryEvaluateRules(ICollection rules, User use .Select(t => t.Trim()) .Contains(comparisonAttributeValue)) { + logger.Log(l + "match"); + return true; } + logger.Log(l + "no match"); + break; case ComparatorEnum.NotIn: @@ -131,26 +170,74 @@ private static bool TryEvaluateRules(ICollection rules, User use .Select(t => t.Trim()) .Contains(comparisonAttributeValue)) { + logger.Log(l + "match"); + return true; } + logger.Log(l + "no match"); + break; case ComparatorEnum.Contains: if (comparisonAttributeValue.Contains(rule.ComparisonValue)) { + logger.Log(l + "match"); + return true; } + logger.Log(l + "no match"); + break; case ComparatorEnum.NotContains: if (!comparisonAttributeValue.Contains(rule.ComparisonValue)) { + logger.Log(l + "match"); + + return true; + } + + logger.Log(l + "no match"); + + break; + case ComparatorEnum.SemVerIn: + case ComparatorEnum.SemVerNotIn: + case ComparatorEnum.SemVerLessThan: + case ComparatorEnum.SemVerLessThanEqual: + case ComparatorEnum.SemVerGreaterThan: + case ComparatorEnum.SemVerGreaterThanEqual: + + if (EvaluateSemVer(comparisonAttributeValue, rule.ComparisonValue, rule.Comparator)) + { + logger.Log(l + "match"); + return true; } + logger.Log(l + "no match"); + break; + + case ComparatorEnum.NumberEqual: + case ComparatorEnum.NumberNotEqual: + case ComparatorEnum.NumberLessThan: + case ComparatorEnum.NumberLessThanEqual: + case ComparatorEnum.NumberGreaterThan: + case ComparatorEnum.NumberGreaterThanEqual: + + if (EvaluateNumber(comparisonAttributeValue, rule.ComparisonValue, rule.Comparator)) + { + logger.Log(l + "match"); + + return true; + } + + logger.Log(l + "no match"); + + break; + default: break; } @@ -160,6 +247,127 @@ private static bool TryEvaluateRules(ICollection rules, User use return false; } + private static bool EvaluateNumber(string s1, string s2, ComparatorEnum comparator) + { + if (!double.TryParse(s1.Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out double d1) + || !double.TryParse(s2.Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out double d2)) + { + return false; + } + + switch (comparator) + { + case ComparatorEnum.NumberEqual: + + return d1 == d2; + + case ComparatorEnum.NumberNotEqual: + + return d1 != d2; + + case ComparatorEnum.NumberLessThan: + + return d1 < d2; + + case ComparatorEnum.NumberLessThanEqual: + + return d1 <= d2; + + case ComparatorEnum.NumberGreaterThan: + + return d1 > d2; + + case ComparatorEnum.NumberGreaterThanEqual: + + return d1 >= d2; + + default: + break; + } + + return false; + } + + private static bool EvaluateSemVer(string s1, string s2, ComparatorEnum comparator) + { + if (SemVersion.TryParse(s1?.Trim(), out SemVersion v1, true)) + { + s2 = s2?.Trim(); + + switch (comparator) + { + case ComparatorEnum.SemVerIn: + + var rsvi = s2 + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => + { + if (SemVersion.TryParse(s.Trim(), out SemVersion ns, true)) + { + return ns; + } + + return null; + }); + + return !rsvi.Contains(null) && rsvi.Contains(v1); + + case ComparatorEnum.SemVerNotIn: + + var rsvni = s2 + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => + { + if (SemVersion.TryParse(s?.Trim(), out SemVersion ns, true)) + { + return ns; + } + + return null; + }); + + return !rsvni.Contains(null) && !rsvni.Contains(v1); + + case ComparatorEnum.SemVerLessThan: + + if (SemVersion.TryParse(s2, out SemVersion v20, true)) + { + return v1 < v20; + } + + break; + case ComparatorEnum.SemVerLessThanEqual: + + if (SemVersion.TryParse(s2, out SemVersion v21, true)) + { + return v1 <= v21; + } + + break; + case ComparatorEnum.SemVerGreaterThan: + + if (SemVersion.TryParse(s2, out SemVersion v22, true)) + { + return v1 > v22; + } + + break; + case ComparatorEnum.SemVerGreaterThanEqual: + + if (SemVersion.TryParse(s2, out SemVersion v23, true)) + { + return v1 >= v23; + } + + break; + default: + break; + } + } + + return false; + } + private static string HashString(string s) { using (var hash = SHA1.Create()) diff --git a/src/ConfigCatClient/Evaluate/Setting.cs b/src/ConfigCatClient/Evaluate/Setting.cs index fe947a2d..4d6dbf4b 100644 --- a/src/ConfigCatClient/Evaluate/Setting.cs +++ b/src/ConfigCatClient/Evaluate/Setting.cs @@ -5,36 +5,46 @@ namespace ConfigCat.Client.Evaluate { internal class Setting { + [JsonProperty(PropertyName = "v")] public string Value { get; set; } + [JsonProperty(PropertyName = "t")] public SettingTypeEnum SettingType { get; set; } + [JsonProperty(PropertyName = "p")] public List RolloutPercentageItems { get; set; } + [JsonProperty(PropertyName = "r")] public List RolloutRules { get; set; } } internal class RolloutPercentageItem { + [JsonProperty(PropertyName = "o")] public short Order { get; set; } - [JsonProperty(PropertyName = "Value")] + [JsonProperty(PropertyName = "v")] public string RawValue { get; private set; } + [JsonProperty(PropertyName = "p")] public int Percentage { get; set; } } internal class RolloutRule { + [JsonProperty(PropertyName = "o")] public short Order { get; set; } + [JsonProperty(PropertyName = "a")] public string ComparisonAttribute { get; set; } + [JsonProperty(PropertyName = "t")] public ComparatorEnum Comparator { get; set; } + [JsonProperty(PropertyName = "c")] public string ComparisonValue { get; set; } - [JsonProperty(PropertyName = "Value")] + [JsonProperty(PropertyName = "v")] public string RawValue { get; private set; } } @@ -57,6 +67,30 @@ internal enum ComparatorEnum : byte Contains = 2, - NotContains = 3 + NotContains = 3, + + SemVerIn = 4, + + SemVerNotIn = 5, + + SemVerLessThan = 6, + + SemVerLessThanEqual = 7, + + SemVerGreaterThan = 8, + + SemVerGreaterThanEqual = 9, + + NumberEqual = 10, + + NumberNotEqual = 11, + + NumberLessThan = 12, + + NumberLessThanEqual = 13, + + NumberGreaterThan = 14, + + NumberGreaterThanEqual = 15, } } \ No newline at end of file diff --git a/src/ConfigCatClient/HttpConfigFetcher.cs b/src/ConfigCatClient/HttpConfigFetcher.cs index 4d29a5de..895b04c7 100644 --- a/src/ConfigCatClient/HttpConfigFetcher.cs +++ b/src/ConfigCatClient/HttpConfigFetcher.cs @@ -19,13 +19,13 @@ internal sealed class HttpConfigFetcher : IConfigFetcher, IDisposable private readonly Uri requestUri; - public HttpConfigFetcher(Uri requestUri, string productVersion, ILoggerFactory loggerFactory, HttpClientHandler httpClientHandler) + public HttpConfigFetcher(Uri requestUri, string productVersion, ILogger logger, HttpClientHandler httpClientHandler) { this.requestUri = requestUri; this.productVersion = productVersion; - this.log = loggerFactory.GetLogger(nameof(HttpConfigFetcher)); + this.log = logger; this.httpClientHandler = httpClientHandler; @@ -61,10 +61,14 @@ public async Task Fetch(ProjectConfig lastConfig) newConfig.JsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); } - else + else if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { - this.log.Warning("Unexpected statuscode - " + response.StatusCode); + newConfig = lastConfig; + this.log.Error("Double-check your API KEY at https://app.configcat.com/apikey"); + } + else + { this.ReInitializeHttpClient(); } } @@ -86,7 +90,10 @@ private void ReInitializeHttpClient() { if (this.httpClientHandler == null) { - this.httpClient = new HttpClient(); + this.httpClient = new HttpClient(new HttpClientHandler + { + AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate + }); } else { @@ -107,4 +114,6 @@ public void Dispose() } } } + + internal sealed class WrapClientHandler : DelegatingHandler { } } \ No newline at end of file diff --git a/src/ConfigCatClient/IConfigCatClient.cs b/src/ConfigCatClient/IConfigCatClient.cs index 887966ab..1277cfb7 100644 --- a/src/ConfigCatClient/IConfigCatClient.cs +++ b/src/ConfigCatClient/IConfigCatClient.cs @@ -9,6 +9,11 @@ namespace ConfigCat.Client /// public interface IConfigCatClient : IDisposable { + /// + /// Set or get logging level + /// + LogLevel LogLevel { get; set; } + /// /// Return a value of the key (Key for programs) /// diff --git a/src/ConfigCatClient/Logging/ConsoleLogger.cs b/src/ConfigCatClient/Logging/ConsoleLogger.cs index 710bf4ee..12074ed0 100644 --- a/src/ConfigCatClient/Logging/ConsoleLogger.cs +++ b/src/ConfigCatClient/Logging/ConsoleLogger.cs @@ -5,17 +5,52 @@ namespace ConfigCat.Client /// /// Write log messages into /// - public class ConsoleLogger : LoggerBase + public class ConsoleLogger : ILogger { /// - public ConsoleLogger(string loggerName) : base(loggerName, LogLevel.Debug) + public LogLevel LogLevel { get; set; } + + /// + /// Create a instance with Warning loglevel + /// + public ConsoleLogger() : this(LogLevel.Warning) { } + + /// + /// Create a instance + /// + /// Log level + public ConsoleLogger(LogLevel logLevel) { + this.LogLevel = logLevel; } /// - protected override void LogMessage(string message) + public void Debug(string message) + { + Console.WriteLine(FormatMessage(LogLevel.Debug, message)); + } + + /// + public void Error(string message) + { + Console.WriteLine(FormatMessage(LogLevel.Error, message)); + } + + /// + public void Information(string message) + { + Console.WriteLine(FormatMessage(LogLevel.Info, message)); + } + + /// + public void Warning(string message) + { + Console.WriteLine(FormatMessage(LogLevel.Warning, message)); + } + + private string FormatMessage(LogLevel logLevel, string message) { - Console.WriteLine(message); + return $"ConfigCat - {logLevel.ToString()} - {message}"; } } } diff --git a/src/ConfigCatClient/Logging/ConsoleLoggerFactory.cs b/src/ConfigCatClient/Logging/ConsoleLoggerFactory.cs deleted file mode 100644 index c8b88103..00000000 --- a/src/ConfigCatClient/Logging/ConsoleLoggerFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace ConfigCat.Client -{ - /// - /// Logger factory class for - /// - public sealed class ConsoleLoggerFactory : ILoggerFactory - { - /// - public ILogger GetLogger(string loggerName) - { - return new ConsoleLogger(loggerName); - } - } -} diff --git a/src/ConfigCatClient/Logging/ILogger.cs b/src/ConfigCatClient/Logging/ILogger.cs index 042fa830..8d80c91a 100644 --- a/src/ConfigCatClient/Logging/ILogger.cs +++ b/src/ConfigCatClient/Logging/ILogger.cs @@ -5,6 +5,11 @@ /// public interface ILogger { + /// + /// Specifies message's filtering + /// + LogLevel LogLevel { get; set; } + /// /// Write debug level message into log /// @@ -27,6 +32,6 @@ public interface ILogger /// Write error level message into log /// /// - void Error(string message); + void Error(string message); } } diff --git a/src/ConfigCatClient/Logging/ILoggerFactory.cs b/src/ConfigCatClient/Logging/ILoggerFactory.cs deleted file mode 100644 index 3b0a519c..00000000 --- a/src/ConfigCatClient/Logging/ILoggerFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace ConfigCat.Client -{ - /// - /// Provides logger factory interface - /// - public interface ILoggerFactory - { - /// - /// Create a ILogger instance by name - /// - /// Name of logger (expample: ClassName, Local unit name) - /// - ILogger GetLogger(string loggerName); - } -} diff --git a/src/ConfigCatClient/Logging/LogLevel.cs b/src/ConfigCatClient/Logging/LogLevel.cs index ee2c28f6..cfcb12f5 100644 --- a/src/ConfigCatClient/Logging/LogLevel.cs +++ b/src/ConfigCatClient/Logging/LogLevel.cs @@ -2,7 +2,7 @@ { /// /// Specifies message's filtering to output for the class. - /// Verbose > Info > Warn > Error > Off + /// Debug > Info > Warning > Error > Off /// public enum LogLevel { @@ -17,7 +17,7 @@ public enum LogLevel /// /// Error and warning messages. /// - Warn = 2, + Warning = 2, /// /// Information, Error and Warning messages. /// diff --git a/src/ConfigCatClient/Logging/LoggerBase.cs b/src/ConfigCatClient/Logging/LoggerBase.cs deleted file mode 100644 index 13b13322..00000000 --- a/src/ConfigCatClient/Logging/LoggerBase.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; - -namespace ConfigCat.Client -{ - /// - /// Provides the abstract base class for the Logger - /// - public abstract class LoggerBase : ILogger - { - private readonly LogLevel logLevel; - - private readonly string loggerName; - - /// - /// Initializes a new instance of the TraceWriterBase class. - /// - /// Name of logger instance - /// Message filter - protected LoggerBase(string loggerName, LogLevel logLevel) - { - this.loggerName = loggerName; - this.logLevel = logLevel; - } - - private bool TargetLogEnabled(LogLevel targetTrace) - { - return (byte)targetTrace <= (byte)this.logLevel; - } - - /// - /// Writes a specified to Logger - /// - /// - protected abstract void LogMessage(string message); - - /// - /// Modify message before write into Logger - /// - /// log level - /// message - /// - protected virtual string FormatMessage(LogLevel logLevel, string message) - { - return $"{DateTime.UtcNow.ToString("yyyy.MM.dd. HH:mm:ss")} - [{logLevel.ToString()}] - {loggerName ?? ""} - {message}"; - } - - /// - /// Write a message into a Logger if a passed is enabled - /// - /// - /// - protected void Log(LogLevel logLevel, string message) - { - if (this.TargetLogEnabled(logLevel)) - { - this.LogMessage(FormatMessage(logLevel, message)); - } - } - - /// - public virtual void Debug(string message) - { - Log(LogLevel.Debug, message); - } - - /// - public virtual void Information(string message) - { - Log(LogLevel.Info, message); - } - - /// - public virtual void Warning(string message) - { - Log(LogLevel.Warn, message); - } - - /// - public virtual void Error(string message) - { - Log(LogLevel.Error, message); - } - } -} diff --git a/src/ConfigCatClient/Logging/LoggerWrapper.cs b/src/ConfigCatClient/Logging/LoggerWrapper.cs new file mode 100644 index 00000000..016a7214 --- /dev/null +++ b/src/ConfigCatClient/Logging/LoggerWrapper.cs @@ -0,0 +1,58 @@ +using System; + +namespace ConfigCat.Client +{ + internal sealed class LoggerWrapper : ILogger + { + private readonly ILogger logger; + + public LogLevel LogLevel { get; set; } + + internal LoggerWrapper(ILogger logger) + { + this.LogLevel = logger.LogLevel; + this.logger = logger; + } + + private bool TargetLogEnabled(LogLevel targetTrace) + { + return (byte)targetTrace <= (byte)this.LogLevel; + } + + /// + public void Debug(string message) + { + if (this.TargetLogEnabled(LogLevel.Debug)) + { + this.logger.Debug(message); + } + } + + /// + public void Information(string message) + { + if (this.TargetLogEnabled(LogLevel.Info)) + { + this.logger.Information(message); + } + } + + /// + public void Warning(string message) + { + if (this.TargetLogEnabled(LogLevel.Warning)) + { + this.logger.Warning(message); + } + } + + /// + public void Error(string message) + { + if (this.TargetLogEnabled(LogLevel.Error)) + { + this.logger.Error(message); + } + } + } +} diff --git a/src/ConfigCatClient/Logging/NullLogger.cs b/src/ConfigCatClient/Logging/NullLogger.cs deleted file mode 100644 index 6e54a35c..00000000 --- a/src/ConfigCatClient/Logging/NullLogger.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace ConfigCat.Client -{ - internal class NullLoggerFactory : ILoggerFactory - { - public ILogger GetLogger(string loggerName) - { - return new NullLogger(); - } - } - - internal class NullLogger : LoggerBase - { - public NullLogger() : base(null, LogLevel.Off) { } - - protected override void LogMessage(string message) { } - } -}