From a129e3a8d6db211bfdc7f9531b3ca34ef949f6ed Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Wed, 29 Apr 2020 21:48:07 +0500 Subject: [PATCH 01/24] Added Optimizely Json --- OptimizelySDK/Entity/FeatureVariable.cs | 3 + OptimizelySDK/Entity/OptimizelyJson.cs | 101 ++++++++++++++++++++++++ OptimizelySDK/OptimizelySDK.csproj | 1 + 3 files changed, 105 insertions(+) create mode 100644 OptimizelySDK/Entity/OptimizelyJson.cs diff --git a/OptimizelySDK/Entity/FeatureVariable.cs b/OptimizelySDK/Entity/FeatureVariable.cs index c0634b9e..06f72b7c 100644 --- a/OptimizelySDK/Entity/FeatureVariable.cs +++ b/OptimizelySDK/Entity/FeatureVariable.cs @@ -22,6 +22,8 @@ public class FeatureVariable : IdKeyEntity public const string INTEGER_TYPE = "integer"; public const string DOUBLE_TYPE = "double"; public const string BOOLEAN_TYPE = "boolean"; + + public const string VARIABLE_SUB_TYPE = "json"; public enum VariableStatus { @@ -32,6 +34,7 @@ public enum VariableStatus public string DefaultValue { get; set; } public string Type { get; set; } + public string SubType { get; set; } public VariableStatus Status { get; set; } /// diff --git a/OptimizelySDK/Entity/OptimizelyJson.cs b/OptimizelySDK/Entity/OptimizelyJson.cs new file mode 100644 index 00000000..24c457fc --- /dev/null +++ b/OptimizelySDK/Entity/OptimizelyJson.cs @@ -0,0 +1,101 @@ +/* + * Copyright 2020, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using OptimizelySDK.Logger; +using System; +using System.Collections.Generic; + +namespace OptimizelySDK.Entity +{ + class OptimizelyJson + { + private ILogger Logger; + private string Payload { get; set; } + private Dictionary Dict { get; set; } + + public OptimizelyJson(string payload, ILogger logger) + { + try + { + Dict = Newtonsoft.Json.JsonConvert.DeserializeObject>(payload); + Payload = payload; + Logger = logger; + } + catch (Exception ex) + { + logger.Log(LogLevel.ERROR, "Provided string could not be converted to map."); + logger.Log(LogLevel.ERROR, ex.Message); + } + } + public OptimizelyJson(Dictionary payload, ILogger logger) + { + try + { + Payload = Newtonsoft.Json.JsonConvert.SerializeObject(payload); + Dict = payload; + Logger = logger; + } + catch (Exception ex) + { + logger.Log(LogLevel.ERROR, "Provided map could not be converted to string."); + logger.Log(LogLevel.ERROR, ex.Message); + } + } + + public string ToString => Payload; + + public Dictionary ToDictionary() + { + return Dict; + } + + /// + /// If JSON Data is {"k1":true, "k2":{"k3":"v3"}} + /// + /// Set jsonPath to "k2" to access {"k3":"v3"} or set it to "k2.k3" to access "v3" + /// Set it to nil or empty to access the entire JSON data. + /// + /// Key path for the value. + /// Value if decoded successfully + public object GetValue(string jsonPath) + { + try + { + string[] path = jsonPath.Split('.'); + Dictionary currentObject = null; + for (int i = 0; i < path.Length - 1; i++) + { + if (currentObject[path[i]] is Dictionary) + { + currentObject = (Dictionary)currentObject[path[i]]; + } + else + { + Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); + return false; + } + } + return (T) currentObject[path[path.Length - 1]]; + } + catch (Exception ex) + { + Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); + Logger.Log(LogLevel.ERROR, ex.Message); + } + return false; + } + } +} diff --git a/OptimizelySDK/OptimizelySDK.csproj b/OptimizelySDK/OptimizelySDK.csproj index 01ff091b..cb2b7185 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -86,6 +86,7 @@ + From 18857e6c31c6d9a0e4d16387e677b3798ec2e9d3 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 30 Apr 2020 02:01:03 +0500 Subject: [PATCH 02/24] Added unit tests --- .../OptimizelySDK.Net35.csproj | 3 + .../OptimizelySDK.Net40.csproj | 3 + .../OptimizelySDK.NetStandard16.csproj | 1 + .../OptimizelySDK.NetStandard20.csproj | 3 + .../EntityTests/OptimizelyJsonTest.cs | 107 ++++++++++++++++++ .../OptimizelySDK.Tests.csproj | 1 + OptimizelySDK/Entity/OptimizelyJson.cs | 24 ++-- 7 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 56e376cc..54a11d72 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -96,6 +96,9 @@ Entity\IdKeyEntity.cs + + + Entity\OptimizelyJson.cs Entity\TrafficAllocation.cs diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index 4d669271..46e9c8e8 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -98,6 +98,9 @@ Entity\IdKeyEntity.cs + + + Entity\OptimizelyJson.cs Entity\TrafficAllocation.cs diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index c51907f0..efa94910 100644 --- a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj +++ b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj @@ -31,6 +31,7 @@ + diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj index ed4ab047..e8ade61b 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -114,6 +114,9 @@ Entity\IdKeyEntity.cs + + + Entity\OptimizelyJson.cs Entity\Rollout.cs diff --git a/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs new file mode 100644 index 00000000..9b64c38d --- /dev/null +++ b/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs @@ -0,0 +1,107 @@ +using Moq; +using NUnit.Framework; +using OptimizelySDK.Entity; +using OptimizelySDK.Logger; +using System.Collections.Generic; + +namespace OptimizelySDK.Tests.EntityTests +{ + [TestFixture] + public class OptimizelyJsonTest + { + private string Payload; + private Dictionary Map; + private Mock LoggerMock; + + [SetUp] + public void Initialize() + { + LoggerMock = new Mock(); + LoggerMock.Setup(i => i.Log(It.IsAny(), It.IsAny())); + + Payload = "{ \"field1\": 1, \"field2\": 2.5, \"field3\": \"three\", \"field4\": {\"inner_field1\":3,\"inner_field2\":[\"1\",\"2\", 3.01, 4.23, true]}, \"field5\": true, }"; + + Map = new Dictionary() { + { "strField", "john doe" }, + { "intField", 12 }, + { "doubleField", 2.23 }, + { "boolField", true}, + { "objectField", new Dictionary () { + { "inner_field_int", 3 }, + { "inner_field_double", 13.21 }, + { "inner_field_string", "john" }, + { "inner_field_boolean", true } + } + } + }; + } + + [Test] + public void TestOptimizelyJsonObjectIsValid() + { + OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, LoggerMock.Object); + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, LoggerMock.Object); + + Assert.IsNotNull(OptimizelyJSONUsingMap); + Assert.IsNotNull(OptimizelyJSONUsingString); + } + [Test] + public void TestToStringReturnValidString() + { + Dictionary map = new Dictionary() { + { "strField", "john doe" }, + { "intField", 12 }, + { "objectField", new Dictionary () { + { "inner_field_int", 3 } + } + } + }; + OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(map, LoggerMock.Object); + string str = OptimizelyJSONUsingMap.ToString(); + string expectedStringObj = "{\"strField\":\"john doe\",\"intField\":12,\"objectField\":{\"inner_field_int\":3}}"; + Assert.AreEqual(expectedStringObj, str); + } + + [Test] + public void TestGettingErrorUponInvalidJsonString() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", LoggerMock.Object); + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); + } + + [Test] + public void TestGettingErrorUponNotFindingValuePath() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", LoggerMock.Object); + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); + } + + [Test] + public void TestOptimizelyJsonGetVariablesWhenSetUsingMap() + { + OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, LoggerMock.Object); + + Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("strField"), "john doe"); + Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("intField"), 12); + Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("doubleField"), 2.23); + Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("boolField"), true); + Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("objectField.inner_field_int"), 3); + Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("objectField.inner_field_double"), 13.21); + Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("objectField.inner_field_string"), "john"); + Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("objectField.inner_field_boolean"), true); + Assert.IsTrue(OptimizelyJSONUsingMap.GetValue>("objectField") is Dictionary); + } + + [Test] + public void TestOptimizelyJsonGetVariablesWhenSetUsingString() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, LoggerMock.Object); + + Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field1"), 1); + Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field2"), 2.5); + Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field3"), "three"); + Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field1"), 3); + Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field2"), new object[] { "1", "2", 3.01, 4.23, true }); + } + } +} \ No newline at end of file diff --git a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index c777eb91..42e489ad 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -77,6 +77,7 @@ + diff --git a/OptimizelySDK/Entity/OptimizelyJson.cs b/OptimizelySDK/Entity/OptimizelyJson.cs index 24c457fc..5c838562 100644 --- a/OptimizelySDK/Entity/OptimizelyJson.cs +++ b/OptimizelySDK/Entity/OptimizelyJson.cs @@ -20,7 +20,7 @@ namespace OptimizelySDK.Entity { - class OptimizelyJson + public class OptimizelyJson { private ILogger Logger; private string Payload { get; set; } @@ -55,7 +55,8 @@ public OptimizelyJson(Dictionary payload, ILogger logger) } } - public string ToString => Payload; + override + public string ToString() { return Payload; } public Dictionary ToDictionary() { @@ -70,22 +71,21 @@ public Dictionary ToDictionary() /// /// Key path for the value. /// Value if decoded successfully - public object GetValue(string jsonPath) + public T GetValue(string jsonPath) { try { string[] path = jsonPath.Split('.'); - Dictionary currentObject = null; + Dictionary currentObject = Dict; for (int i = 0; i < path.Length - 1; i++) { - if (currentObject[path[i]] is Dictionary) + if (currentObject[path[i]] is Newtonsoft.Json.Linq.JObject) { - currentObject = (Dictionary)currentObject[path[i]]; - } - else - { - Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); - return false; + currentObject = ((Newtonsoft.Json.Linq.JObject)currentObject[path[i]]).ToObject>(); + } + else + { + currentObject = currentObject[path[i]] as Dictionary; } } return (T) currentObject[path[path.Length - 1]]; @@ -95,7 +95,7 @@ public object GetValue(string jsonPath) Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); Logger.Log(LogLevel.ERROR, ex.Message); } - return false; + return default; } } } From c988dd0dee002c1c81a38eb0a092360056e9b267 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 30 Apr 2020 20:32:09 +0500 Subject: [PATCH 03/24] indentation fixes and small other fixes --- .../EntityTests/OptimizelyJsonTest.cs | 32 +++++++++++++++---- OptimizelySDK/Entity/FeatureVariable.cs | 3 -- OptimizelySDK/Entity/OptimizelyJson.cs | 18 ++++++----- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs index 9b64c38d..c8709e7e 100644 --- a/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs +++ b/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs @@ -1,4 +1,21 @@ -using Moq; +/** + * + * Copyright 2020, Optimizely and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Moq; using NUnit.Framework; using OptimizelySDK.Entity; using OptimizelySDK.Logger; @@ -20,7 +37,7 @@ public void Initialize() LoggerMock.Setup(i => i.Log(It.IsAny(), It.IsAny())); Payload = "{ \"field1\": 1, \"field2\": 2.5, \"field3\": \"three\", \"field4\": {\"inner_field1\":3,\"inner_field2\":[\"1\",\"2\", 3.01, 4.23, true]}, \"field5\": true, }"; - + Map = new Dictionary() { { "strField", "john doe" }, { "intField", 12 }, @@ -42,8 +59,8 @@ public void TestOptimizelyJsonObjectIsValid() OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, LoggerMock.Object); OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, LoggerMock.Object); - Assert.IsNotNull(OptimizelyJSONUsingMap); - Assert.IsNotNull(OptimizelyJSONUsingString); + Assert.IsNotNull(OptimizelyJSONUsingMap); + Assert.IsNotNull(OptimizelyJSONUsingString); } [Test] public void TestToStringReturnValidString() @@ -54,7 +71,7 @@ public void TestToStringReturnValidString() { "objectField", new Dictionary () { { "inner_field_int", 3 } } - } + } }; OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(map, LoggerMock.Object); string str = OptimizelyJSONUsingMap.ToString(); @@ -101,7 +118,8 @@ public void TestOptimizelyJsonGetVariablesWhenSetUsingString() Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field2"), 2.5); Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field3"), "three"); Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field1"), 3); - Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field2"), new object[] { "1", "2", 3.01, 4.23, true }); + // TO-DO: uncomment the line once the issue of converting double and int gets resolved. + //Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field2"), new object[] { "1", "2", 3.01, 4.23, true }); } } -} \ No newline at end of file +} diff --git a/OptimizelySDK/Entity/FeatureVariable.cs b/OptimizelySDK/Entity/FeatureVariable.cs index 06f72b7c..c0634b9e 100644 --- a/OptimizelySDK/Entity/FeatureVariable.cs +++ b/OptimizelySDK/Entity/FeatureVariable.cs @@ -22,8 +22,6 @@ public class FeatureVariable : IdKeyEntity public const string INTEGER_TYPE = "integer"; public const string DOUBLE_TYPE = "double"; public const string BOOLEAN_TYPE = "boolean"; - - public const string VARIABLE_SUB_TYPE = "json"; public enum VariableStatus { @@ -34,7 +32,6 @@ public enum VariableStatus public string DefaultValue { get; set; } public string Type { get; set; } - public string SubType { get; set; } public VariableStatus Status { get; set; } /// diff --git a/OptimizelySDK/Entity/OptimizelyJson.cs b/OptimizelySDK/Entity/OptimizelyJson.cs index 5c838562..847bf33f 100644 --- a/OptimizelySDK/Entity/OptimizelyJson.cs +++ b/OptimizelySDK/Entity/OptimizelyJson.cs @@ -17,10 +17,11 @@ using OptimizelySDK.Logger; using System; using System.Collections.Generic; +using Newtonsoft.Json.Linq; namespace OptimizelySDK.Entity { - public class OptimizelyJson + public class OptimizelyJson { private ILogger Logger; private string Payload { get; set; } @@ -56,7 +57,8 @@ public OptimizelyJson(Dictionary payload, ILogger logger) } override - public string ToString() { return Payload; } + public string ToString() + { return Payload; } public Dictionary ToDictionary() { @@ -79,16 +81,16 @@ public T GetValue(string jsonPath) Dictionary currentObject = Dict; for (int i = 0; i < path.Length - 1; i++) { - if (currentObject[path[i]] is Newtonsoft.Json.Linq.JObject) + if (currentObject[path[i]] is JObject) + { + currentObject = ((JObject)currentObject[path[i]]).ToObject>(); + } + else { - currentObject = ((Newtonsoft.Json.Linq.JObject)currentObject[path[i]]).ToObject>(); - } - else - { currentObject = currentObject[path[i]] as Dictionary; } } - return (T) currentObject[path[path.Length - 1]]; + return (T)currentObject[path[path.Length - 1]]; } catch (Exception ex) { From cb4842712b69b185f5f3ed3d1d8dfd7d8792765f Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 30 Apr 2020 23:00:57 +0500 Subject: [PATCH 04/24] Added GetFeatureVariableJson support and unit test function --- .../EntityTests/FeatureVariableTest.cs | 2 ++ OptimizelySDK.Tests/OptimizelyTest.cs | 26 +++++++++++++++++++ OptimizelySDK/Entity/FeatureVariable.cs | 4 +++ OptimizelySDK/Optimizely.cs | 26 ++++++++++++++++--- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs b/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs index e4324652..b44b3194 100644 --- a/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs +++ b/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs @@ -30,6 +30,7 @@ public void TestFeatureVariableTypeName() Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.DOUBLE_TYPE), "GetFeatureVariableDouble"); Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.INTEGER_TYPE), "GetFeatureVariableInteger"); Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.STRING_TYPE), "GetFeatureVariableString"); + Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.JSON_TYPE), "GetFeatureVariableJSON"); } [Test] @@ -39,6 +40,7 @@ public void TestConstantValues() Assert.AreEqual(FeatureVariable.DOUBLE_TYPE, "double"); Assert.AreEqual(FeatureVariable.INTEGER_TYPE, "integer"); Assert.AreEqual(FeatureVariable.STRING_TYPE, "string"); + Assert.AreEqual(FeatureVariable.JSON_TYPE, "json"); } } } diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 84c77604..b99e9b26 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1316,6 +1316,32 @@ public void TestGetFeatureVariableStringReturnsCorrectValue() Assert.Null(OptimizelyMock.Object.GetFeatureVariableString(featureKey, variableKeyNull, TestUserId, null)); } + [Test] + public void TestGetFeatureVariableJSONReturnsCorrectValue() + { + var featureKey = "featureKey"; + var variableKeyString = "varJSONString1"; + var variableKeyIntString = "varJSONString2"; + var variableKeyDouble = "varJSONDouble"; + var variableKeyNull = "varNull"; + var featureVariableType = "json"; + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyString, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(new OptimizelyJson("{\"string\": \"Test String\"}", LoggerMock.Object)); + Assert.AreEqual("Test String", OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null).GetValue("string")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyIntString, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(new OptimizelyJson("{ \"integer\": 123 }", LoggerMock.Object)); + Assert.AreEqual(123, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null).GetValue("integer")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyDouble, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(new OptimizelyJson("{ \"double\": 123.28 }", LoggerMock.Object)); + Assert.AreEqual(123.28, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null).GetValue("double")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNull, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(null); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyNull, TestUserId, null)); + } #region Feature Toggle Tests [Test] diff --git a/OptimizelySDK/Entity/FeatureVariable.cs b/OptimizelySDK/Entity/FeatureVariable.cs index c0634b9e..027c70f6 100644 --- a/OptimizelySDK/Entity/FeatureVariable.cs +++ b/OptimizelySDK/Entity/FeatureVariable.cs @@ -22,6 +22,7 @@ public class FeatureVariable : IdKeyEntity public const string INTEGER_TYPE = "integer"; public const string DOUBLE_TYPE = "double"; public const string BOOLEAN_TYPE = "boolean"; + public const string JSON_TYPE = "json"; public enum VariableStatus { @@ -32,6 +33,7 @@ public enum VariableStatus public string DefaultValue { get; set; } public string Type { get; set; } + public string SubType { get; set; } public VariableStatus Status { get; set; } /// @@ -50,6 +52,8 @@ public static string GetFeatureVariableTypeName(string variableType) return "GetFeatureVariableInteger"; case STRING_TYPE: return "GetFeatureVariableString"; + case JSON_TYPE: + return "GetFeatureVariableJSON"; default: return null; } diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs index d227597d..86a51273 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -28,6 +28,7 @@ using OptimizelySDK.Config; using OptimizelySDK.Event; using OptimizelySDK.OptlyConfig; +using System.Net; namespace OptimizelySDK { @@ -548,9 +549,12 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var } else if (featureVariable.Type != variableType) { - Logger.Log(LogLevel.ERROR, - $@"Variable is of type ""{featureVariable.Type}"", but you requested it as type ""{variableType}""."); - return default(T); + if (featureVariable.SubType != variableType || featureVariable.Type != FeatureVariable.STRING_TYPE) + { + Logger.Log(LogLevel.ERROR, + $@"Variable is of type ""{featureVariable.Type}"", but you requested it as type ""{variableType}""."); + return default(T); + } } var featureEnabled = false; @@ -662,6 +666,19 @@ public string GetFeatureVariableString(string featureKey, string variableKey, st return GetFeatureVariableValueForType(featureKey, variableKey, userId, userAttributes, FeatureVariable.STRING_TYPE); } + /// + /// Gets json sub type feature variable value. + /// + /// The feature flag key + /// The variable key + /// The user ID + /// The user's attributes + /// OptimizelyJson | Feature variable value or null + public OptimizelyJson GetFeatureVariableJSON(string featureKey, string variableKey, string userId, UserAttributes userAttributes = null) + { + return GetFeatureVariableValueForType(featureKey, variableKey, userId, userAttributes, FeatureVariable.JSON_TYPE); + } + /// /// Sends impression event. /// @@ -806,6 +823,9 @@ private object GetTypeCastedVariableValue(string value, string type) case FeatureVariable.STRING_TYPE: result = value; break; + case FeatureVariable.JSON_TYPE: + result = new OptimizelyJson(value, Logger); + break; } if (result == null) From 5735882653e422e33835f66959fe4a06c88b6fd6 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Mon, 4 May 2020 21:43:41 +0500 Subject: [PATCH 05/24] Added Unit tests --- OptimizelySDK.Tests/OptimizelyTest.cs | 54 +++++++++++++++++++++++++++ OptimizelySDK.Tests/TestData.json | 15 ++++++++ 2 files changed, 69 insertions(+) diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index b99e9b26..f0fc38a3 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -197,6 +197,7 @@ public void TestInvalidInstanceLogMessages() Assert.IsNull(optimizely.GetFeatureVariableString(string.Empty, string.Empty, string.Empty)); Assert.IsNull(optimizely.GetFeatureVariableDouble(string.Empty, string.Empty, string.Empty)); Assert.IsNull(optimizely.GetFeatureVariableInteger(string.Empty, string.Empty, string.Empty)); + Assert.IsNull(optimizely.GetFeatureVariableJSON(string.Empty, string.Empty, string.Empty)); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided 'datafile' has invalid schema."), Times.Once); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetVariation'."), Times.Once); @@ -208,6 +209,7 @@ public void TestInvalidInstanceLogMessages() LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableString'."), Times.Once); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableDouble'."), Times.Once); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableInteger'."), Times.Once); + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableJSON'."), Times.Once); } [Test] @@ -1245,6 +1247,25 @@ public void TestGetFeatureVariableStringFRCulture() Assert.AreEqual(stringValueFR, "cta_1"); } + [Test] + public void TestGetFeatureVariableJSONFRCulture() + { + SetCulture("en-US"); + var fallbackConfigManager = new FallbackProjectConfigManager(Config); + + var optimizely = new Optimizely(fallbackConfigManager); + + var optimizelyJsonValue = optimizely.GetFeatureVariableJSON("string_single_variable_feature", "json_var", "testUser1"); + + Assert.AreEqual(optimizelyJsonValue.GetValue("int_var"), 1); + Assert.AreEqual(optimizelyJsonValue.GetValue("boolean_key"), false); + + SetCulture("fr-FR"); + var optimizelyJsonValueFR = optimizely.GetFeatureVariableJSON("string_single_variable_feature", "json_var", "testUser1"); + Assert.AreEqual(optimizelyJsonValueFR.GetValue("int_var"), 1); + Assert.AreEqual(optimizelyJsonValueFR.GetValue("boolean_key"), false); + } + [Test] public void TestGetFeatureVariableDoubleReturnsCorrectValue() { @@ -1470,6 +1491,38 @@ public void TestGetFeatureVariableBooleanReturnsRightValueWhenUserBuckedIntoRoll LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""true"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); } + [Test] + public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBuckedIntoRolloutAndVariationIsToggleOn() + { + var featureKey = "string_single_variable_feature"; + var featureFlag = Config.GetFeatureFlagFromKey(featureKey); + var variableKey = "json_var"; + var expectedStringValue = "cta_4"; + var expectedIntValue = 4; + var experiment = Config.GetRolloutFromId("166661").Experiments[0]; + var variation = Config.GetVariationFromKey(experiment.Key, "177775"); + var decision = new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT); + var userAttributes = new UserAttributes + { + { "device_type", "iPhone" }, + { "company", "Optimizely" }, + { "location", "San Francisco" } + }; + + DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, TestUserId, Config, userAttributes)).Returns(decision); + + var optly = Helper.CreatePrivateOptimizely(); + + optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); + optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); + + var variableValue = (OptimizelyJson)optly.Invoke("GetFeatureVariableJSON", featureKey, variableKey, TestUserId, userAttributes); + Assert.AreEqual(expectedIntValue, variableValue.GetValue("int_var")); + Assert.AreEqual(expectedStringValue, variableValue.GetValue("string_var")); + + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + } + [Test] public void TestGetFeatureVariableStringReturnsRightValueWhenUserBuckedIntoRolloutAndVariationIsToggleOn() { @@ -1632,6 +1685,7 @@ public void TestGetFeatureVariableValueForTypeGivenInvalidVariableType() Assert.IsNull(Optimizely.GetFeatureVariableValueForType("boolean_single_variable_feature", "boolean_variable", TestUserId, null, variableTypeDouble)); Assert.IsNull(Optimizely.GetFeatureVariableValueForType("integer_single_variable_feature", "integer_variable", TestUserId, null, variableTypeString)); Assert.IsNull(Optimizely.GetFeatureVariableValueForType("string_single_variable_feature", "string_variable", TestUserId, null, variableTypeInt)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType("string_single_variable_feature", "json_var", TestUserId, null, variableTypeInt)); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, $@"Variable is of type ""double"", but you requested it as type ""{variableTypeBool}"".")); diff --git a/OptimizelySDK.Tests/TestData.json b/OptimizelySDK.Tests/TestData.json index 0e44dc56..95607843 100644 --- a/OptimizelySDK.Tests/TestData.json +++ b/OptimizelySDK.Tests/TestData.json @@ -171,6 +171,10 @@ { "id": "155558", "value": "cta_1" + }, + { + "id": "17014990011", + "value": "{\"int_var\": 1, \"boolean_key\": false}" } ], "featureEnabled": true @@ -589,6 +593,13 @@ "key": "string_variable", "type": "string", "defaultValue": "wingardium leviosa" + }, + { + "defaultValue": "{\"int_var\": 5212, \"boolean_key\": true}", + "type": "string", + "subType": "json", + "id": "17014990011", + "key": "json_var" } ] }, @@ -816,6 +827,10 @@ { "id": "155558", "value": "cta_4" + }, + { + "id": "17014990011", + "value": "{\"int_var\": 4 , \"string_var\": \"cta_4\"}" } ], "featureEnabled": true From 0f2e63cf4214069e57fd7cd0a02cd78a991d0c6d Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Mon, 4 May 2020 21:52:06 +0500 Subject: [PATCH 06/24] default literal was not supported so Updated it to default(T) --- OptimizelySDK/Entity/OptimizelyJson.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OptimizelySDK/Entity/OptimizelyJson.cs b/OptimizelySDK/Entity/OptimizelyJson.cs index 847bf33f..3c86f23f 100644 --- a/OptimizelySDK/Entity/OptimizelyJson.cs +++ b/OptimizelySDK/Entity/OptimizelyJson.cs @@ -97,7 +97,7 @@ public T GetValue(string jsonPath) Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); Logger.Log(LogLevel.ERROR, ex.Message); } - return default; + return default(T); } } } From 439d5fa0c1b3230adafadf171e66a4bfd1b6c2ba Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Tue, 5 May 2020 16:51:54 +0500 Subject: [PATCH 07/24] comments resolved --- .../OptimizelySDK.Net35.csproj | 4 +-- .../OptimizelySDK.Net40.csproj | 4 +-- .../OptimizelySDK.NetStandard16.csproj | 2 +- .../OptimizelySDK.NetStandard20.csproj | 2 +- .../{EntityTests => }/OptimizelyJsonTest.cs | 23 ++++++++----- .../OptimizelySDK.Tests.csproj | 2 +- OptimizelySDK/{Entity => }/OptimizelyJson.cs | 33 +++++++++++-------- OptimizelySDK/OptimizelySDK.csproj | 2 +- 8 files changed, 42 insertions(+), 30 deletions(-) rename OptimizelySDK.Tests/{EntityTests => }/OptimizelyJsonTest.cs (88%) rename OptimizelySDK/{Entity => }/OptimizelyJson.cs (76%) diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 54a11d72..6453f2da 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -97,8 +97,8 @@ Entity\IdKeyEntity.cs - - Entity\OptimizelyJson.cs + + OptimizelyJson.cs Entity\TrafficAllocation.cs diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index 46e9c8e8..c261b579 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -99,8 +99,8 @@ Entity\IdKeyEntity.cs - - Entity\OptimizelyJson.cs + + OptimizelyJson.cs Entity\TrafficAllocation.cs diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index efa94910..07eb88d8 100644 --- a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj +++ b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj @@ -31,7 +31,7 @@ - + diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj index e8ade61b..c4634d9f 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -115,7 +115,7 @@ Entity\IdKeyEntity.cs - + Entity\OptimizelyJson.cs diff --git a/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/OptimizelyJsonTest.cs similarity index 88% rename from OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs rename to OptimizelySDK.Tests/OptimizelyJsonTest.cs index c8709e7e..ba424112 100644 --- a/OptimizelySDK.Tests/EntityTests/OptimizelyJsonTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJsonTest.cs @@ -17,11 +17,12 @@ using Moq; using NUnit.Framework; -using OptimizelySDK.Entity; +using OptimizelySDK.ErrorHandler; using OptimizelySDK.Logger; +using System; using System.Collections.Generic; -namespace OptimizelySDK.Tests.EntityTests +namespace OptimizelySDK.Tests { [TestFixture] public class OptimizelyJsonTest @@ -29,10 +30,14 @@ public class OptimizelyJsonTest private string Payload; private Dictionary Map; private Mock LoggerMock; + private Mock ErrorHandlerMock; [SetUp] public void Initialize() { + ErrorHandlerMock = new Mock(); + ErrorHandlerMock.Setup(e => e.HandleError(It.IsAny())); + LoggerMock = new Mock(); LoggerMock.Setup(i => i.Log(It.IsAny(), It.IsAny())); @@ -56,8 +61,8 @@ public void Initialize() [Test] public void TestOptimizelyJsonObjectIsValid() { - OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, LoggerMock.Object); - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, LoggerMock.Object); + OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); Assert.IsNotNull(OptimizelyJSONUsingMap); Assert.IsNotNull(OptimizelyJSONUsingString); @@ -73,7 +78,7 @@ public void TestToStringReturnValidString() } } }; - OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(map, LoggerMock.Object); + OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(map, ErrorHandlerMock.Object, LoggerMock.Object); string str = OptimizelyJSONUsingMap.ToString(); string expectedStringObj = "{\"strField\":\"john doe\",\"intField\":12,\"objectField\":{\"inner_field_int\":3}}"; Assert.AreEqual(expectedStringObj, str); @@ -82,21 +87,21 @@ public void TestToStringReturnValidString() [Test] public void TestGettingErrorUponInvalidJsonString() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", LoggerMock.Object); + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); } [Test] public void TestGettingErrorUponNotFindingValuePath() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", LoggerMock.Object); + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); } [Test] public void TestOptimizelyJsonGetVariablesWhenSetUsingMap() { - OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, LoggerMock.Object); + OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, ErrorHandlerMock.Object, LoggerMock.Object); Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("strField"), "john doe"); Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("intField"), 12); @@ -112,7 +117,7 @@ public void TestOptimizelyJsonGetVariablesWhenSetUsingMap() [Test] public void TestOptimizelyJsonGetVariablesWhenSetUsingString() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, LoggerMock.Object); + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field1"), 1); Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field2"), 2.5); diff --git a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index 42e489ad..786b8eaf 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -77,7 +77,7 @@ - + diff --git a/OptimizelySDK/Entity/OptimizelyJson.cs b/OptimizelySDK/OptimizelyJson.cs similarity index 76% rename from OptimizelySDK/Entity/OptimizelyJson.cs rename to OptimizelySDK/OptimizelyJson.cs index 3c86f23f..b5b09231 100644 --- a/OptimizelySDK/Entity/OptimizelyJson.cs +++ b/OptimizelySDK/OptimizelyJson.cs @@ -18,47 +18,54 @@ using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; +using OptimizelySDK.ErrorHandler; -namespace OptimizelySDK.Entity +namespace OptimizelySDK { public class OptimizelyJson { private ILogger Logger; + private IErrorHandler ErrorHandler; + private string Payload { get; set; } private Dictionary Dict { get; set; } - public OptimizelyJson(string payload, ILogger logger) + public OptimizelyJson(string payload, IErrorHandler errorHandler, ILogger logger) { try { + ErrorHandler = errorHandler; + Logger = logger; Dict = Newtonsoft.Json.JsonConvert.DeserializeObject>(payload); Payload = payload; - Logger = logger; } - catch (Exception ex) + catch (Exception exception) { logger.Log(LogLevel.ERROR, "Provided string could not be converted to map."); - logger.Log(LogLevel.ERROR, ex.Message); + ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); } } - public OptimizelyJson(Dictionary payload, ILogger logger) + public OptimizelyJson(Dictionary dict, IErrorHandler errorHandler, ILogger logger) { try { - Payload = Newtonsoft.Json.JsonConvert.SerializeObject(payload); - Dict = payload; + ErrorHandler = errorHandler; Logger = logger; + Payload = Newtonsoft.Json.JsonConvert.SerializeObject(dict); + Dict = dict; } - catch (Exception ex) + catch (Exception exception) { logger.Log(LogLevel.ERROR, "Provided map could not be converted to string."); - logger.Log(LogLevel.ERROR, ex.Message); + ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); } } override public string ToString() - { return Payload; } + { + return Payload; + } public Dictionary ToDictionary() { @@ -92,10 +99,10 @@ public T GetValue(string jsonPath) } return (T)currentObject[path[path.Length - 1]]; } - catch (Exception ex) + catch (Exception exception) { Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); - Logger.Log(LogLevel.ERROR, ex.Message); + ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); } return default(T); } diff --git a/OptimizelySDK/OptimizelySDK.csproj b/OptimizelySDK/OptimizelySDK.csproj index cb2b7185..239bf02a 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -86,7 +86,7 @@ - + From 4c316f4f212d20c5c51dad62145210ba7291e1e4 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Wed, 6 May 2020 20:35:28 +0500 Subject: [PATCH 08/24] Converted all Jobjects to dict and Jarray object to List --- OptimizelySDK.Tests/OptimizelyJsonTest.cs | 5 ++--- OptimizelySDK/OptimizelyJson.cs | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/OptimizelyJsonTest.cs index ba424112..eb309808 100644 --- a/OptimizelySDK.Tests/OptimizelyJsonTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJsonTest.cs @@ -41,7 +41,7 @@ public void Initialize() LoggerMock = new Mock(); LoggerMock.Setup(i => i.Log(It.IsAny(), It.IsAny())); - Payload = "{ \"field1\": 1, \"field2\": 2.5, \"field3\": \"three\", \"field4\": {\"inner_field1\":3,\"inner_field2\":[\"1\",\"2\", 3.01, 4.23, true]}, \"field5\": true, }"; + Payload = "{ \"field1\": 1, \"field2\": 2.5, \"field3\": \"three\", \"field4\": {\"inner_field1\":3,\"inner_field2\":[\"1\",\"2\", 3, 4.23, true]}, \"field5\": true, }"; Map = new Dictionary() { { "strField", "john doe" }, @@ -123,8 +123,7 @@ public void TestOptimizelyJsonGetVariablesWhenSetUsingString() Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field2"), 2.5); Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field3"), "three"); Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field1"), 3); - // TO-DO: uncomment the line once the issue of converting double and int gets resolved. - //Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field2"), new object[] { "1", "2", 3.01, 4.23, true }); + Assert.AreEqual(OptimizelyJSONUsingString.GetValue>("field4.inner_field2"), new List() { "1", "2", 3, 4.23, true }); } } } diff --git a/OptimizelySDK/OptimizelyJson.cs b/OptimizelySDK/OptimizelyJson.cs index b5b09231..ab9e36f1 100644 --- a/OptimizelySDK/OptimizelyJson.cs +++ b/OptimizelySDK/OptimizelyJson.cs @@ -19,6 +19,8 @@ using System.Collections.Generic; using Newtonsoft.Json.Linq; using OptimizelySDK.ErrorHandler; +using System.Linq; +using Newtonsoft.Json; namespace OptimizelySDK { @@ -36,7 +38,7 @@ public OptimizelyJson(string payload, IErrorHandler errorHandler, ILogger logger { ErrorHandler = errorHandler; Logger = logger; - Dict = Newtonsoft.Json.JsonConvert.DeserializeObject>(payload); + Dict = (Dictionary)ToCollections(JObject.Parse(payload)); Payload = payload; } catch (Exception exception) @@ -51,7 +53,7 @@ public OptimizelyJson(Dictionary dict, IErrorHandler errorHandle { ErrorHandler = errorHandler; Logger = logger; - Payload = Newtonsoft.Json.JsonConvert.SerializeObject(dict); + Payload = JsonConvert.SerializeObject(dict); Dict = dict; } catch (Exception exception) @@ -60,6 +62,12 @@ public OptimizelyJson(Dictionary dict, IErrorHandler errorHandle ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); } } + public static object ToCollections(object o) + { + if (o is JObject jo) return jo.ToObject>().ToDictionary(k => k.Key, v => ToCollections(v.Value)); + if (o is JArray ja) return ja.ToObject>().Select(ToCollections).ToList(); + return o; + } override public string ToString() @@ -88,14 +96,7 @@ public T GetValue(string jsonPath) Dictionary currentObject = Dict; for (int i = 0; i < path.Length - 1; i++) { - if (currentObject[path[i]] is JObject) - { - currentObject = ((JObject)currentObject[path[i]]).ToObject>(); - } - else - { - currentObject = currentObject[path[i]] as Dictionary; - } + currentObject = currentObject[path[i]] as Dictionary; } return (T)currentObject[path[path.Length - 1]]; } From 8e84f4301c6ea63d0942c02d9ab0a5433231e007 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Wed, 6 May 2020 20:43:28 +0500 Subject: [PATCH 09/24] Added invalid cast exception class in optizelyException --- OptimizelySDK/Exceptions/OptimizelyException.cs | 8 ++++++++ OptimizelySDK/OptimizelyJson.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/OptimizelySDK/Exceptions/OptimizelyException.cs b/OptimizelySDK/Exceptions/OptimizelyException.cs index 56c7b3ed..881eaca4 100644 --- a/OptimizelySDK/Exceptions/OptimizelyException.cs +++ b/OptimizelySDK/Exceptions/OptimizelyException.cs @@ -33,6 +33,14 @@ public OptimizelyRuntimeException(string message) { } } + + public class InvalidCastException : OptimizelyException + { + public InvalidCastException(string message) + : base(message) + { + } + } public class InvalidAttributeException : OptimizelyException { diff --git a/OptimizelySDK/OptimizelyJson.cs b/OptimizelySDK/OptimizelyJson.cs index ab9e36f1..15b12db5 100644 --- a/OptimizelySDK/OptimizelyJson.cs +++ b/OptimizelySDK/OptimizelyJson.cs @@ -103,7 +103,7 @@ public T GetValue(string jsonPath) catch (Exception exception) { Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); - ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); + ErrorHandler.HandleError(new Exceptions.InvalidCastException(exception.Message)); } return default(T); } From 96cfd82397dfc4d4cd530eb7dcab5a21bb301793 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 7 May 2020 06:00:11 +0500 Subject: [PATCH 10/24] Added addition unit tests and added support of casting dictionary to entity classes --- OptimizelySDK.Tests/OptimizelyJsonTest.cs | 81 ++++++++++++++++++- .../Exceptions/OptimizelyException.cs | 4 +- OptimizelySDK/OptimizelyJson.cs | 60 ++++++++++---- 3 files changed, 128 insertions(+), 17 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/OptimizelyJsonTest.cs index eb309808..bf3134a6 100644 --- a/OptimizelySDK.Tests/OptimizelyJsonTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJsonTest.cs @@ -42,7 +42,6 @@ public void Initialize() LoggerMock.Setup(i => i.Log(It.IsAny(), It.IsAny())); Payload = "{ \"field1\": 1, \"field2\": 2.5, \"field3\": \"three\", \"field4\": {\"inner_field1\":3,\"inner_field2\":[\"1\",\"2\", 3, 4.23, true]}, \"field5\": true, }"; - Map = new Dictionary() { { "strField", "john doe" }, { "intField", 12 }, @@ -84,6 +83,13 @@ public void TestToStringReturnValidString() Assert.AreEqual(expectedStringObj, str); } + [Test] + public void TestGettingErrorUponNotSupportedJsonDictionaryType() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); + } + [Test] public void TestGettingErrorUponInvalidJsonString() { @@ -125,5 +131,78 @@ public void TestOptimizelyJsonGetVariablesWhenSetUsingString() Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field1"), 3); Assert.AreEqual(OptimizelyJSONUsingString.GetValue>("field4.inner_field2"), new List() { "1", "2", 3, 4.23, true }); } + + [Test] + public void TestGetValueReturnsEntireDictWhenJsonPathIsEmptyAndTypeIsValid() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = OptimizelyJSONUsingString.GetValue>(""); + Assert.NotNull(expectedValue); + Assert.AreEqual(expectedValue["field1"], 1); + Assert.AreEqual(expectedValue["field2"], 2.5); + Assert.AreEqual(expectedValue["field3"], "three"); + Assert.AreEqual(((Dictionary)expectedValue["field4"])["inner_field1"], 3); + Assert.AreEqual(((Dictionary)expectedValue["field4"])["inner_field2"], new List() { "1", "2", 3, 4.23, true }); + } + + [Test] + public void TestGetValueReturnsDefaultValueWhenJsonIsInvalid() + { + string payload = "{ \"field1\" : {1:\"Csharp\", 2:\"Java\"} }"; + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = OptimizelyJSONUsingString.GetValue>("field1"); + // Even though above given JSON is not valid, newtonsoft is parsing it so + Assert.IsNotNull(expectedValue); + } + + [Test] + public void TestGetValueReturnsDefaultValueWhenTypeIsInvalid() + { + string payload = "{ \"field1\" : {\"1\":\"Csharp\",\"2\":\"Java\"} }"; + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = OptimizelyJSONUsingString.GetValue>("field1"); + Assert.IsNotNull(expectedValue); + } + + [Test] + public void TestGetValueReturnsNullWhenJsonPathIsEmptyAndTypeIsOfObject() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = OptimizelyJSONUsingString.GetValue(""); + Assert.NotNull(expectedValue); + } + + [Test] + public void TestGetValueReturnsDefaultValueWhenJsonPathIsEmptyAndTypeIsNotValid() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = OptimizelyJSONUsingString.GetValue(""); + Assert.IsNull(expectedValue); + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."), Times.Once); + } + + [Test] + public void TestGetValueReturnsDefaultValueWhenJsonPathIsInvalid() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = OptimizelyJSONUsingString.GetValue("field11"); + Assert.IsNull(expectedValue); + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), Times.Once); + } + + class Field4 { + public long inner_field1 { get; set; } + public InnerField2 inner_field2 { get; set; } + } + class InnerField2 : List { } + + [Test] + public void TestGetValueReturnsUsingGivenClassType() + { + OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = OptimizelyJSONUsingString.GetValue("field4"); + Assert.AreEqual(expectedValue.inner_field1, 3); + Assert.AreEqual(expectedValue.inner_field2, new List() { "1", "2", 3, 4.23, true }); + } } } diff --git a/OptimizelySDK/Exceptions/OptimizelyException.cs b/OptimizelySDK/Exceptions/OptimizelyException.cs index 881eaca4..12e4652b 100644 --- a/OptimizelySDK/Exceptions/OptimizelyException.cs +++ b/OptimizelySDK/Exceptions/OptimizelyException.cs @@ -34,9 +34,9 @@ public OptimizelyRuntimeException(string message) } } - public class InvalidCastException : OptimizelyException + public class InvalidJsonException : OptimizelyException { - public InvalidCastException(string message) + public InvalidJsonException(string message) : base(message) { } diff --git a/OptimizelySDK/OptimizelyJson.cs b/OptimizelySDK/OptimizelyJson.cs index 15b12db5..015c49b9 100644 --- a/OptimizelySDK/OptimizelyJson.cs +++ b/OptimizelySDK/OptimizelyJson.cs @@ -38,13 +38,13 @@ public OptimizelyJson(string payload, IErrorHandler errorHandler, ILogger logger { ErrorHandler = errorHandler; Logger = logger; - Dict = (Dictionary)ToCollections(JObject.Parse(payload)); + Dict = (Dictionary)ConvertIntoCollection(JObject.Parse(payload)); Payload = payload; } catch (Exception exception) { logger.Log(LogLevel.ERROR, "Provided string could not be converted to map."); - ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); + ErrorHandler.HandleError(new Exceptions.InvalidJsonException(exception.Message)); } } public OptimizelyJson(Dictionary dict, IErrorHandler errorHandler, ILogger logger) @@ -59,16 +59,10 @@ public OptimizelyJson(Dictionary dict, IErrorHandler errorHandle catch (Exception exception) { logger.Log(LogLevel.ERROR, "Provided map could not be converted to string."); - ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); + ErrorHandler.HandleError(new Exceptions.InvalidJsonException(exception.Message)); } } - public static object ToCollections(object o) - { - if (o is JObject jo) return jo.ToObject>().ToDictionary(k => k.Key, v => ToCollections(v.Value)); - if (o is JArray ja) return ja.ToObject>().Select(ToCollections).ToList(); - return o; - } - + override public string ToString() { @@ -84,7 +78,7 @@ public Dictionary ToDictionary() /// If JSON Data is {"k1":true, "k2":{"k3":"v3"}} /// /// Set jsonPath to "k2" to access {"k3":"v3"} or set it to "k2.k3" to access "v3" - /// Set it to nil or empty to access the entire JSON data. + /// Set it to null or empty to access the entire JSON data but type must be Dictionary as generic type. /// /// Key path for the value. /// Value if decoded successfully @@ -92,20 +86,58 @@ public T GetValue(string jsonPath) { try { + if (string.IsNullOrWhiteSpace(jsonPath)) + { + return (T)(object)Dict; + } string[] path = jsonPath.Split('.'); + Dictionary currentObject = Dict; for (int i = 0; i < path.Length - 1; i++) { - currentObject = currentObject[path[i]] as Dictionary; + currentObject = currentObject[path[i]] as Dictionary; } - return (T)currentObject[path[path.Length - 1]]; + return GetObject(currentObject[path[path.Length - 1]]); + } + catch (KeyNotFoundException exception) + { + Logger.Log(LogLevel.ERROR, "Value for JSON key not found."); + ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); } catch (Exception exception) { Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); - ErrorHandler.HandleError(new Exceptions.InvalidCastException(exception.Message)); + ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); } return default(T); } + + private T GetObject(object o) + { + try + { + return (T)o; + } + catch + { + } + + var deserializedObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(o)); + return deserializedObj; + } + + private object ConvertIntoCollection(object o) + { + if (o is JObject jo) + { + return jo.ToObject>().ToDictionary(k => k.Key, v => ConvertIntoCollection(v.Value)); + } + else if (o is JArray ja) + { + return ja.ToObject>().Select(ConvertIntoCollection).ToList(); + } + return o; + } + } } From 7efb98f3c1c856555d9825441c371c43653f3b16 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 7 May 2020 06:06:14 +0500 Subject: [PATCH 11/24] indentation fix --- OptimizelySDK.Tests/OptimizelyJsonTest.cs | 5 +++-- OptimizelySDK/OptimizelyJson.cs | 11 +++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/OptimizelyJsonTest.cs index bf3134a6..a4db1ccf 100644 --- a/OptimizelySDK.Tests/OptimizelyJsonTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJsonTest.cs @@ -144,7 +144,7 @@ public void TestGetValueReturnsEntireDictWhenJsonPathIsEmptyAndTypeIsValid() Assert.AreEqual(((Dictionary)expectedValue["field4"])["inner_field1"], 3); Assert.AreEqual(((Dictionary)expectedValue["field4"])["inner_field2"], new List() { "1", "2", 3, 4.23, true }); } - + [Test] public void TestGetValueReturnsDefaultValueWhenJsonIsInvalid() { @@ -190,7 +190,8 @@ public void TestGetValueReturnsDefaultValueWhenJsonPathIsInvalid() LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), Times.Once); } - class Field4 { + class Field4 + { public long inner_field1 { get; set; } public InnerField2 inner_field2 { get; set; } } diff --git a/OptimizelySDK/OptimizelyJson.cs b/OptimizelySDK/OptimizelyJson.cs index 015c49b9..5edadb9f 100644 --- a/OptimizelySDK/OptimizelyJson.cs +++ b/OptimizelySDK/OptimizelyJson.cs @@ -62,10 +62,9 @@ public OptimizelyJson(Dictionary dict, IErrorHandler errorHandle ErrorHandler.HandleError(new Exceptions.InvalidJsonException(exception.Message)); } } - - override - public string ToString() - { + + override public string ToString() + { return Payload; } @@ -118,8 +117,8 @@ private T GetObject(object o) { return (T)o; } - catch - { + catch + { } var deserializedObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(o)); From 31a7dca0cc2d4ade8eae9e5d7c1b24954fd2fbf6 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 7 May 2020 06:16:16 +0500 Subject: [PATCH 12/24] changed IsNullOrWhiteSpace into IsNullOrEmpty --- OptimizelySDK/OptimizelyJson.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OptimizelySDK/OptimizelyJson.cs b/OptimizelySDK/OptimizelyJson.cs index 5edadb9f..bbb092bb 100644 --- a/OptimizelySDK/OptimizelyJson.cs +++ b/OptimizelySDK/OptimizelyJson.cs @@ -85,7 +85,7 @@ public T GetValue(string jsonPath) { try { - if (string.IsNullOrWhiteSpace(jsonPath)) + if (string.IsNullOrEmpty(jsonPath)) { return (T)(object)Dict; } From 348acf170de4e3d08064e7314e68a287315e6e02 Mon Sep 17 00:00:00 2001 From: Sohail Hussain Date: Thu, 7 May 2020 01:22:40 -0700 Subject: [PATCH 13/24] added a unit test --- OptimizelySDK.Tests/OptimizelyJsonTest.cs | 44 +++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/OptimizelyJsonTest.cs index a4db1ccf..56b6ae47 100644 --- a/OptimizelySDK.Tests/OptimizelyJsonTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJsonTest.cs @@ -24,6 +24,31 @@ namespace OptimizelySDK.Tests { + class ParentJson + { + public string strField { get; set; } + public int intField { get; set; } + public double doubleField { get; set; } + public bool boolField { get; set; } + public ObjectJson objectField { get; set; } + + } + class ObjectJson + { + public int inner_field_int { get; set; } + public double inner_field_double { get; set; } + public string inner_field_string {get;set;} + public bool inner_field_boolean { get; set; } + } + + class Field4 + { + public long inner_field1 { get; set; } + public InnerField2 inner_field2 { get; set; } + } + class InnerField2 : List { } + + [TestFixture] public class OptimizelyJsonTest { @@ -190,12 +215,7 @@ public void TestGetValueReturnsDefaultValueWhenJsonPathIsInvalid() LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), Times.Once); } - class Field4 - { - public long inner_field1 { get; set; } - public InnerField2 inner_field2 { get; set; } - } - class InnerField2 : List { } + [Test] public void TestGetValueReturnsUsingGivenClassType() @@ -205,5 +225,17 @@ public void TestGetValueReturnsUsingGivenClassType() Assert.AreEqual(expectedValue.inner_field1, 3); Assert.AreEqual(expectedValue.inner_field2, new List() { "1", "2", 3, 4.23, true }); } + + [Test] + public void TestGetValueReturnsCastedObject() + { + var optimizelyJson = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + // TODO: All values must be same as payload. + var expectedValue = new ParentJson(); + + var actualValue = optimizelyJson.GetValue(null); + Assert.IsTrue(TestData.CompareObjects(actualValue, expectedValue)); + + } } } From a6d1ddd97841133275eeac991644fa39e1ee5940 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 7 May 2020 18:15:47 +0500 Subject: [PATCH 14/24] Refact --- OptimizelySDK.Tests/OptimizelyJsonTest.cs | 100 +++++++++++----------- OptimizelySDK/OptimizelyJson.cs | 15 ++-- 2 files changed, 53 insertions(+), 62 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/OptimizelyJsonTest.cs index 56b6ae47..f73df065 100644 --- a/OptimizelySDK.Tests/OptimizelyJsonTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJsonTest.cs @@ -85,16 +85,16 @@ public void Initialize() [Test] public void TestOptimizelyJsonObjectIsValid() { - OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, ErrorHandlerMock.Object, LoggerMock.Object); - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingMap = new OptimizelyJson(Map, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); - Assert.IsNotNull(OptimizelyJSONUsingMap); - Assert.IsNotNull(OptimizelyJSONUsingString); + Assert.IsNotNull(optimizelyJSONUsingMap); + Assert.IsNotNull(optimizelyJSONUsingString); } [Test] public void TestToStringReturnValidString() { - Dictionary map = new Dictionary() { + var map = new Dictionary() { { "strField", "john doe" }, { "intField", 12 }, { "objectField", new Dictionary () { @@ -102,8 +102,8 @@ public void TestToStringReturnValidString() } } }; - OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(map, ErrorHandlerMock.Object, LoggerMock.Object); - string str = OptimizelyJSONUsingMap.ToString(); + var optimizelyJSONUsingMap = new OptimizelyJson(map, ErrorHandlerMock.Object, LoggerMock.Object); + string str = optimizelyJSONUsingMap.ToString(); string expectedStringObj = "{\"strField\":\"john doe\",\"intField\":12,\"objectField\":{\"inner_field_int\":3}}"; Assert.AreEqual(expectedStringObj, str); } @@ -111,71 +111,68 @@ public void TestToStringReturnValidString() [Test] public void TestGettingErrorUponNotSupportedJsonDictionaryType() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); } [Test] public void TestGettingErrorUponInvalidJsonString() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); } [Test] public void TestGettingErrorUponNotFindingValuePath() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); } [Test] public void TestOptimizelyJsonGetVariablesWhenSetUsingMap() { - OptimizelyJson OptimizelyJSONUsingMap = new OptimizelyJson(Map, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingMap = new OptimizelyJson(Map, ErrorHandlerMock.Object, LoggerMock.Object); - Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("strField"), "john doe"); - Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("intField"), 12); - Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("doubleField"), 2.23); - Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("boolField"), true); - Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("objectField.inner_field_int"), 3); - Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("objectField.inner_field_double"), 13.21); - Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("objectField.inner_field_string"), "john"); - Assert.AreEqual(OptimizelyJSONUsingMap.GetValue("objectField.inner_field_boolean"), true); - Assert.IsTrue(OptimizelyJSONUsingMap.GetValue>("objectField") is Dictionary); + Assert.AreEqual(optimizelyJSONUsingMap.GetValue("strField"), "john doe"); + Assert.AreEqual(optimizelyJSONUsingMap.GetValue("intField"), 12); + Assert.AreEqual(optimizelyJSONUsingMap.GetValue("doubleField"), 2.23); + Assert.AreEqual(optimizelyJSONUsingMap.GetValue("boolField"), true); + Assert.AreEqual(optimizelyJSONUsingMap.GetValue("objectField.inner_field_int"), 3); + Assert.AreEqual(optimizelyJSONUsingMap.GetValue("objectField.inner_field_double"), 13.21); + Assert.AreEqual(optimizelyJSONUsingMap.GetValue("objectField.inner_field_string"), "john"); + Assert.AreEqual(optimizelyJSONUsingMap.GetValue("objectField.inner_field_boolean"), true); + Assert.IsTrue(optimizelyJSONUsingMap.GetValue>("objectField") is Dictionary); } [Test] public void TestOptimizelyJsonGetVariablesWhenSetUsingString() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); - Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field1"), 1); - Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field2"), 2.5); - Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field3"), "three"); - Assert.AreEqual(OptimizelyJSONUsingString.GetValue("field4.inner_field1"), 3); - Assert.AreEqual(OptimizelyJSONUsingString.GetValue>("field4.inner_field2"), new List() { "1", "2", 3, 4.23, true }); + Assert.AreEqual(optimizelyJSONUsingString.GetValue("field1"), 1); + Assert.AreEqual(optimizelyJSONUsingString.GetValue("field2"), 2.5); + Assert.AreEqual(optimizelyJSONUsingString.GetValue("field3"), "three"); + Assert.AreEqual(optimizelyJSONUsingString.GetValue("field4.inner_field1"), 3); + Assert.True(TestData.CompareObjects(optimizelyJSONUsingString.GetValue>("field4.inner_field2"), new List() { "1", "2", 3, 4.23, true })); } [Test] public void TestGetValueReturnsEntireDictWhenJsonPathIsEmptyAndTypeIsValid() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = OptimizelyJSONUsingString.GetValue>(""); + var optimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDict = optimizelyJSONUsingString.ToDictionary(); + var expectedValue = optimizelyJSONUsingString.GetValue>(""); Assert.NotNull(expectedValue); - Assert.AreEqual(expectedValue["field1"], 1); - Assert.AreEqual(expectedValue["field2"], 2.5); - Assert.AreEqual(expectedValue["field3"], "three"); - Assert.AreEqual(((Dictionary)expectedValue["field4"])["inner_field1"], 3); - Assert.AreEqual(((Dictionary)expectedValue["field4"])["inner_field2"], new List() { "1", "2", 3, 4.23, true }); + Assert.True(TestData.CompareObjects(expectedValue, actualDict)); } [Test] public void TestGetValueReturnsDefaultValueWhenJsonIsInvalid() { - string payload = "{ \"field1\" : {1:\"Csharp\", 2:\"Java\"} }"; - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = OptimizelyJSONUsingString.GetValue>("field1"); + var payload = "{ \"field1\" : {1:\"Csharp\", 2:\"Java\"} }"; + var optimizelyJSONUsingString = new OptimizelyJson(payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = optimizelyJSONUsingString.GetValue>("field1"); // Even though above given JSON is not valid, newtonsoft is parsing it so Assert.IsNotNull(expectedValue); } @@ -183,25 +180,25 @@ public void TestGetValueReturnsDefaultValueWhenJsonIsInvalid() [Test] public void TestGetValueReturnsDefaultValueWhenTypeIsInvalid() { - string payload = "{ \"field1\" : {\"1\":\"Csharp\",\"2\":\"Java\"} }"; - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = OptimizelyJSONUsingString.GetValue>("field1"); + var payload = "{ \"field1\" : {\"1\":\"Csharp\",\"2\":\"Java\"} }"; + var optimizelyJSONUsingString = new OptimizelyJson(payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = optimizelyJSONUsingString.GetValue>("field1"); Assert.IsNotNull(expectedValue); } [Test] public void TestGetValueReturnsNullWhenJsonPathIsEmptyAndTypeIsOfObject() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = OptimizelyJSONUsingString.GetValue(""); + var optimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = optimizelyJSONUsingString.GetValue(""); Assert.NotNull(expectedValue); } [Test] public void TestGetValueReturnsDefaultValueWhenJsonPathIsEmptyAndTypeIsNotValid() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = OptimizelyJSONUsingString.GetValue(""); + var optimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = optimizelyJSONUsingString.GetValue(""); Assert.IsNull(expectedValue); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."), Times.Once); } @@ -209,8 +206,8 @@ public void TestGetValueReturnsDefaultValueWhenJsonPathIsEmptyAndTypeIsNotValid( [Test] public void TestGetValueReturnsDefaultValueWhenJsonPathIsInvalid() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = OptimizelyJSONUsingString.GetValue("field11"); + var optimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = optimizelyJSONUsingString.GetValue("field11"); Assert.IsNull(expectedValue); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), Times.Once); } @@ -220,8 +217,9 @@ public void TestGetValueReturnsDefaultValueWhenJsonPathIsInvalid() [Test] public void TestGetValueReturnsUsingGivenClassType() { - OptimizelyJson OptimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = OptimizelyJSONUsingString.GetValue("field4"); + var optimizelyJSONUsingString = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = optimizelyJSONUsingString.GetValue("field4"); + Assert.AreEqual(expectedValue.inner_field1, 3); Assert.AreEqual(expectedValue.inner_field2, new List() { "1", "2", 3, 4.23, true }); } @@ -229,13 +227,11 @@ public void TestGetValueReturnsUsingGivenClassType() [Test] public void TestGetValueReturnsCastedObject() { - var optimizelyJson = new OptimizelyJson(Payload, ErrorHandlerMock.Object, LoggerMock.Object); - // TODO: All values must be same as payload. - var expectedValue = new ParentJson(); - + var optimizelyJson = new OptimizelyJson(Map, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = optimizelyJson.ToDictionary(); var actualValue = optimizelyJson.GetValue(null); + Assert.IsTrue(TestData.CompareObjects(actualValue, expectedValue)); - } } } diff --git a/OptimizelySDK/OptimizelyJson.cs b/OptimizelySDK/OptimizelyJson.cs index bbb092bb..9240e071 100644 --- a/OptimizelySDK/OptimizelyJson.cs +++ b/OptimizelySDK/OptimizelyJson.cs @@ -87,11 +87,11 @@ public T GetValue(string jsonPath) { if (string.IsNullOrEmpty(jsonPath)) { - return (T)(object)Dict; + return GetObject(Dict); } - string[] path = jsonPath.Split('.'); + var path = jsonPath.Split('.'); - Dictionary currentObject = Dict; + var currentObject = Dict; for (int i = 0; i < path.Length - 1; i++) { currentObject = currentObject[path[i]] as Dictionary; @@ -113,15 +113,10 @@ public T GetValue(string jsonPath) private T GetObject(object o) { - try - { - return (T)o; - } - catch + if (!(o is T deserializedObj)) { + deserializedObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(o)); } - - var deserializedObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(o)); return deserializedObj; } From aa6de6b716c0a09e7e4ae28cc0058025c0d0b901 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 7 May 2020 18:25:13 +0500 Subject: [PATCH 15/24] updated header --- OptimizelySDK/Exceptions/OptimizelyException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OptimizelySDK/Exceptions/OptimizelyException.cs b/OptimizelySDK/Exceptions/OptimizelyException.cs index 12e4652b..5d9d4cc6 100644 --- a/OptimizelySDK/Exceptions/OptimizelyException.cs +++ b/OptimizelySDK/Exceptions/OptimizelyException.cs @@ -1,5 +1,5 @@ /* - * Copyright 2017, Optimizely + * Copyright 2017, 2020, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 8355cde9547d0e875d694c9277599f92c7e2f879 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 7 May 2020 22:00:11 +0500 Subject: [PATCH 16/24] if notification value is optimizelyJson --- OptimizelySDK/Optimizely.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs index 4b98115b..817c0e9c 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -603,7 +603,7 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var { "featureKey", featureKey }, { "featureEnabled", featureEnabled }, { "variableKey", variableKey }, - { "variableValue", typeCastedValue }, + { "variableValue", typeCastedValue is OptimizelyJson? ((OptimizelyJson)typeCastedValue).ToDictionary() : typeCastedValue }, { "variableType", variableType.ToString().ToLower() }, { "source", decision?.Source }, { "sourceInfo", sourceInfo }, From a8aec1b9702152e2a7a8bde121184e2e2c90ac5e Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Thu, 7 May 2020 23:07:50 +0500 Subject: [PATCH 17/24] added errorhandler mock verifier --- OptimizelySDK.Tests/OptimizelyJsonTest.cs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyJsonTest.cs b/OptimizelySDK.Tests/OptimizelyJsonTest.cs index f73df065..fafbeb4b 100644 --- a/OptimizelySDK.Tests/OptimizelyJsonTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJsonTest.cs @@ -18,6 +18,7 @@ using Moq; using NUnit.Framework; using OptimizelySDK.ErrorHandler; +using OptimizelySDK.Exceptions; using OptimizelySDK.Logger; using System; using System.Collections.Generic; @@ -108,25 +109,12 @@ public void TestToStringReturnValidString() Assert.AreEqual(expectedStringObj, str); } - [Test] - public void TestGettingErrorUponNotSupportedJsonDictionaryType() - { - var optimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); - } - [Test] public void TestGettingErrorUponInvalidJsonString() { var optimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); - } - - [Test] - public void TestGettingErrorUponNotFindingValuePath() - { - var optimizelyJSONUsingString = new OptimizelyJson("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); + ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); } [Test] @@ -201,6 +189,7 @@ public void TestGetValueReturnsDefaultValueWhenJsonPathIsEmptyAndTypeIsNotValid( var expectedValue = optimizelyJSONUsingString.GetValue(""); Assert.IsNull(expectedValue); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."), Times.Once); + ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); } [Test] @@ -210,10 +199,9 @@ public void TestGetValueReturnsDefaultValueWhenJsonPathIsInvalid() var expectedValue = optimizelyJSONUsingString.GetValue("field11"); Assert.IsNull(expectedValue); LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), Times.Once); + ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); } - - [Test] public void TestGetValueReturnsUsingGivenClassType() { From 3db6dbfbcda7c154d74ec5118d233f831e65257c Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Fri, 8 May 2020 02:52:27 +0500 Subject: [PATCH 18/24] Added additonal comments to explain functions further --- OptimizelySDK/OptimizelyJson.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/OptimizelySDK/OptimizelyJson.cs b/OptimizelySDK/OptimizelyJson.cs index 9240e071..398a075a 100644 --- a/OptimizelySDK/OptimizelyJson.cs +++ b/OptimizelySDK/OptimizelyJson.cs @@ -47,6 +47,7 @@ public OptimizelyJson(string payload, IErrorHandler errorHandler, ILogger logger ErrorHandler.HandleError(new Exceptions.InvalidJsonException(exception.Message)); } } + public OptimizelyJson(Dictionary dict, IErrorHandler errorHandler, ILogger logger) { try @@ -74,6 +75,9 @@ public Dictionary ToDictionary() } /// + /// Returns the value from dictionary of given jsonPath (Seperated by ".") in the provided type T. + /// + /// Example: /// If JSON Data is {"k1":true, "k2":{"k3":"v3"}} /// /// Set jsonPath to "k2" to access {"k3":"v3"} or set it to "k2.k3" to access "v3" @@ -120,6 +124,11 @@ private T GetObject(object o) return deserializedObj; } + /// + /// This will convert all the given JObjects datatype variables into Dictionaries and JArray objects into List. + /// + /// object containing JObject and JArray datatype objects + /// Dictionary object private object ConvertIntoCollection(object o) { if (o is JObject jo) @@ -132,6 +141,5 @@ private object ConvertIntoCollection(object o) } return o; } - } } From beee956cf5aee85a484f744354514900d887178f Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Fri, 8 May 2020 07:29:47 +0500 Subject: [PATCH 19/24] setting type = subtype while parsing if its json --- OptimizelySDK.Tests/OptimizelyTest.cs | 2 +- OptimizelySDK/Entity/FeatureFlag.cs | 7 +++++++ OptimizelySDK/Optimizely.cs | 11 ++++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 7f932a38..75314f7c 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1704,7 +1704,7 @@ public void TestUnsupportedVariableType() Assert.IsNull(featureVariableStringRandomType); var featureVariableStringJsonType = Optimizely.GetFeatureVariableString("unsupported_variabletype", "string_json_key", TestUserId); - Assert.AreEqual(featureVariableStringJsonType, "{\"myvalue\": \"jsonValue\"}"); + Assert.IsNull(featureVariableStringJsonType); } // Should return default value and log message when feature is not enabled for the user. diff --git a/OptimizelySDK/Entity/FeatureFlag.cs b/OptimizelySDK/Entity/FeatureFlag.cs index 2067f17e..b5b609cc 100644 --- a/OptimizelySDK/Entity/FeatureFlag.cs +++ b/OptimizelySDK/Entity/FeatureFlag.cs @@ -34,6 +34,13 @@ public List Variables } set { + foreach (FeatureVariable variable in value) + { + if (variable.SubType == FeatureVariable.JSON_TYPE && variable.Type == FeatureVariable.STRING_TYPE) + { + variable.Type = variable.SubType; + } + } _Variables = value; // Generating Feature Variable key map. diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs index 817c0e9c..297a4f7f 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -548,13 +548,10 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var return default(T); } else if (featureVariable.Type != variableType) - { - if (featureVariable.SubType != variableType || featureVariable.Type != FeatureVariable.STRING_TYPE) - { - Logger.Log(LogLevel.ERROR, - $@"Variable is of type ""{featureVariable.Type}"", but you requested it as type ""{variableType}""."); - return default(T); - } + { + Logger.Log(LogLevel.ERROR, + $@"Variable is of type ""{featureVariable.Type}"", but you requested it as type ""{variableType}""."); + return default(T); } var featureEnabled = false; From 8e087b4e0bee55aa6b8db2431c8ac8c20ce9cc96 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Mon, 11 May 2020 20:09:00 +0500 Subject: [PATCH 20/24] refact --- OptimizelySDK/Entity/FeatureFlag.cs | 7 ----- OptimizelySDK/Entity/FeatureVariable.cs | 34 +++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/OptimizelySDK/Entity/FeatureFlag.cs b/OptimizelySDK/Entity/FeatureFlag.cs index b5b609cc..2067f17e 100644 --- a/OptimizelySDK/Entity/FeatureFlag.cs +++ b/OptimizelySDK/Entity/FeatureFlag.cs @@ -34,13 +34,6 @@ public List Variables } set { - foreach (FeatureVariable variable in value) - { - if (variable.SubType == FeatureVariable.JSON_TYPE && variable.Type == FeatureVariable.STRING_TYPE) - { - variable.Type = variable.SubType; - } - } _Variables = value; // Generating Feature Variable key map. diff --git a/OptimizelySDK/Entity/FeatureVariable.cs b/OptimizelySDK/Entity/FeatureVariable.cs index 027c70f6..33f69a72 100644 --- a/OptimizelySDK/Entity/FeatureVariable.cs +++ b/OptimizelySDK/Entity/FeatureVariable.cs @@ -32,8 +32,38 @@ public enum VariableStatus public string DefaultValue { get; set; } - public string Type { get; set; } - public string SubType { get; set; } + + private string _subType; + + public string SubType + { + get + { + return _subType; + } + set + { + _subType = value; + } + } + + private string _type; + public string Type + { + get + { + if (_type == STRING_TYPE && _subType == JSON_TYPE) + { + return JSON_TYPE; + } + return _type; + } + set + { + _type = value; + } + } + public VariableStatus Status { get; set; } /// From 34765fc664f58834350a693b2ef9b83abe6b5c8c Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Tue, 12 May 2020 22:09:02 +0500 Subject: [PATCH 21/24] Added additional tests --- OptimizelySDK.Tests/OptimizelyTest.cs | 57 ++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 75314f7c..3999ee73 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1250,20 +1250,31 @@ public void TestGetFeatureVariableStringFRCulture() [Test] public void TestGetFeatureVariableJSONFRCulture() { - SetCulture("en-US"); var fallbackConfigManager = new FallbackProjectConfigManager(Config); + var expectedDict = new Dictionary() + { + { "int_var", 1 }, + { "boolean_key", false} + }; + + SetCulture("en-US"); var optimizely = new Optimizely(fallbackConfigManager); var optimizelyJsonValue = optimizely.GetFeatureVariableJSON("string_single_variable_feature", "json_var", "testUser1"); + Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.ToDictionary(), expectedDict)); Assert.AreEqual(optimizelyJsonValue.GetValue("int_var"), 1); Assert.AreEqual(optimizelyJsonValue.GetValue("boolean_key"), false); - + Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.GetValue(""), expectedDict)); + SetCulture("fr-FR"); var optimizelyJsonValueFR = optimizely.GetFeatureVariableJSON("string_single_variable_feature", "json_var", "testUser1"); + + Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.ToDictionary(), expectedDict)); Assert.AreEqual(optimizelyJsonValueFR.GetValue("int_var"), 1); Assert.AreEqual(optimizelyJsonValueFR.GetValue("boolean_key"), false); + Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.GetValue(""), expectedDict)); } [Test] @@ -1363,6 +1374,48 @@ public void TestGetFeatureVariableJSONReturnsCorrectValue() It.IsAny(), featureVariableType)).Returns(null); Assert.Null(OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyNull, TestUserId, null)); } + + [Test] + public void TestGetFeatureVariableJSONReturnsCorrectValueWhenInitializedUsingDictionary() + { + var featureKey = "featureKey"; + var variableKeyString = "varJSONString1"; + var variableKeyIntString = "varJSONString2"; + var variableKeyDouble = "varJSONDouble"; + var variableKeyBoolean = "varJSONBoolean"; + var variableKeyNull = "varNull"; + var featureVariableType = "json"; + + var expectedStringDict = new Dictionary() { { "string", "Test String" } }; + var expectedIntegerDict = new Dictionary() { { "integer", 123 } }; + var expectedDoubleDict = new Dictionary() { { "double", 123.28 } }; + var expectedBooleanDict = new Dictionary() { { "boolean", true } }; + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyString, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(new OptimizelyJson(expectedStringDict, ErrorHandlerMock.Object, LoggerMock.Object)); + Assert.IsTrue(TestData.CompareObjects(expectedStringDict, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null).ToDictionary())); + Assert.AreEqual("Test String", OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null).GetValue("string")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyIntString, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(new OptimizelyJson(expectedIntegerDict, ErrorHandlerMock.Object, LoggerMock.Object)); + Assert.IsTrue(TestData.CompareObjects(expectedIntegerDict, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null).ToDictionary())); + Assert.AreEqual(123, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null).GetValue("integer")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyDouble, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(new OptimizelyJson(expectedDoubleDict, ErrorHandlerMock.Object, LoggerMock.Object)); + Assert.IsTrue(TestData.CompareObjects(expectedDoubleDict, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null).ToDictionary())); + Assert.AreEqual(123.28, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null).GetValue("double")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyBoolean, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(new OptimizelyJson(expectedBooleanDict, ErrorHandlerMock.Object, LoggerMock.Object)); + Assert.IsTrue(TestData.CompareObjects(expectedBooleanDict, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyBoolean, TestUserId, null).ToDictionary())); + Assert.AreEqual(true, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyBoolean, TestUserId, null).GetValue("boolean")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNull, It.IsAny(), + It.IsAny(), featureVariableType)).Returns(null); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyNull, TestUserId, null)); + } + #region Feature Toggle Tests [Test] From 618f6bae62a8abf91c32540863834606f4075f69 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Wed, 13 May 2020 21:29:11 +0500 Subject: [PATCH 22/24] Added getFeatureVariableJsonListener Test --- OptimizelySDK.Tests/OptimizelyTest.cs | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 3999ee73..bc62dc94 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -62,6 +62,7 @@ public class OptimizelyTest const string FEATUREVARIABLE_INTEGERTYPE = "integer"; const string FEATUREVARIABLE_DOUBLETYPE = "double"; const string FEATUREVARIABLE_STRINGTYPE = "string"; + const string FEATUREVARIABLE_JSONTYPE = "json"; #region Test Life Cycle [SetUp] @@ -2765,6 +2766,55 @@ public void TestGetFeatureVariableDoubleSendsNotificationWhenUserBuckedIntoFeatu NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, new UserAttributes(), It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); } + [Test] + public void TestGetFeatureVariableJsonSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() + { + var featureKey = "string_single_variable_feature"; + var featureFlag = Config.GetFeatureFlagFromKey(featureKey); + var variableKey = "json_var"; + var expectedDict = new Dictionary() + { + { "int_var", 4 }, + { "string_var", "cta_4"} + }; + var experiment = Config.GetRolloutFromId("166661").Experiments[0]; + var variation = Config.GetVariationFromKey(experiment.Key, "177775"); + var decision = new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT); + var userAttributes = new UserAttributes + { + { "device_type", "iPhone" }, + { "company", "Optimizely" }, + { "location", "San Francisco" } + }; + + DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, TestUserId, Config, userAttributes)).Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny>())); + + var optly = Helper.CreatePrivateOptimizely(); + + optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); + var optStronglyTyped = optly.GetObject() as Optimizely; + + optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); + optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + + var variableValue = (OptimizelyJson)optly.Invoke("GetFeatureVariableJSON", featureKey, variableKey, TestUserId, userAttributes); + Assert.IsTrue(TestData.CompareObjects(expectedDict, variableValue.ToDictionary())); + var decisionInfo = new Dictionary + { + { "featureKey", featureKey }, + { "featureEnabled", true }, + { "variableKey", variableKey }, + { "variableValue", expectedDict }, + { "variableType", FEATUREVARIABLE_JSONTYPE }, + { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, + { "sourceInfo", new Dictionary() }, + }; + + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + } + [Test] public void TestGetFeatureVariableIntegerSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() { From 8508a44a1d4cec9e7ea3b721ac7b3b3400381259 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Fri, 15 May 2020 00:59:48 +0500 Subject: [PATCH 23/24] changed unsupported type json to regex as we are now supporting json --- OptimizelySDK.Tests/OptimizelyTest.cs | 5 +++-- OptimizelySDK.Tests/TestData.json | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index bc62dc94..1413189d 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1757,8 +1757,9 @@ public void TestUnsupportedVariableType() var featureVariableStringRandomType = Optimizely.GetFeatureVariableString("", "any_key", TestUserId); Assert.IsNull(featureVariableStringRandomType); - var featureVariableStringJsonType = Optimizely.GetFeatureVariableString("unsupported_variabletype", "string_json_key", TestUserId); - Assert.IsNull(featureVariableStringJsonType); + // This is to test that only json subtype is parsing and all other will subtype will be stringify + var featureVariableStringRegexSubType = Optimizely.GetFeatureVariableString("unsupported_variabletype", "string_regex_key", TestUserId); + Assert.AreEqual(featureVariableStringRegexSubType, "^\\d+(\\.\\d+)?"); } // Should return default value and log message when feature is not enabled for the user. diff --git a/OptimizelySDK.Tests/TestData.json b/OptimizelySDK.Tests/TestData.json index 95607843..8c668f52 100644 --- a/OptimizelySDK.Tests/TestData.json +++ b/OptimizelySDK.Tests/TestData.json @@ -665,10 +665,10 @@ }, { "id": "255555", - "key": "string_json_key", + "key": "string_regex_key", "type": "string", - "defaultValue": "{\"myvalue\": \"jsonValue\"}", - "subType": "json" + "defaultValue": "^\\d+(\\.\\d+)?", + "subType": "regex" } ] From 4758fbdc3ca38db6481f5e44437fec8f46062de4 Mon Sep 17 00:00:00 2001 From: muhammadnoman Date: Mon, 18 May 2020 10:41:53 +0500 Subject: [PATCH 24/24] Added Test and set type = json instead of subtype --- OptimizelySDK.Tests/OptimizelyTest.cs | 34 ++++++++++++++++++++++++++- OptimizelySDK.Tests/TestData.json | 10 ++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 1413189d..69dc28d6 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1546,7 +1546,7 @@ public void TestGetFeatureVariableBooleanReturnsRightValueWhenUserBuckedIntoRoll } [Test] - public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBuckedIntoRolloutAndVariationIsToggleOn() + public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRolloutAndVariationIsToggleOn() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -1577,6 +1577,38 @@ public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBuckedIntoRollout LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); } + [Test] + public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRolloutAndVariationIsToggleOnTypeIsJson() + { + var featureKey = "string_single_variable_feature"; + var featureFlag = Config.GetFeatureFlagFromKey(featureKey); + var variableKey = "true_json_var"; + var expectedStringValue = "cta_5"; + var expectedIntValue = 5; + var experiment = Config.GetRolloutFromId("166661").Experiments[0]; + var variation = Config.GetVariationFromKey(experiment.Key, "177775"); + var decision = new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT); + var userAttributes = new UserAttributes + { + { "device_type", "iPhone" }, + { "company", "Optimizely" }, + { "location", "San Francisco" } + }; + + DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, TestUserId, Config, userAttributes)).Returns(decision); + + var optly = Helper.CreatePrivateOptimizely(); + + optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); + optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); + + var variableValue = (OptimizelyJson)optly.Invoke("GetFeatureVariableJSON", featureKey, variableKey, TestUserId, userAttributes); + Assert.AreEqual(expectedIntValue, variableValue.GetValue("int_var")); + Assert.AreEqual(expectedStringValue, variableValue.GetValue("string_var")); + + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + } + [Test] public void TestGetFeatureVariableStringReturnsRightValueWhenUserBuckedIntoRolloutAndVariationIsToggleOn() { diff --git a/OptimizelySDK.Tests/TestData.json b/OptimizelySDK.Tests/TestData.json index 8c668f52..11f3decb 100644 --- a/OptimizelySDK.Tests/TestData.json +++ b/OptimizelySDK.Tests/TestData.json @@ -600,6 +600,12 @@ "subType": "json", "id": "17014990011", "key": "json_var" + }, + { + "defaultValue": "{\"int_var\": 123, \"boolean_key\": true}", + "type": "json", + "id": "170149900112", + "key": "true_json_var" } ] }, @@ -831,6 +837,10 @@ { "id": "17014990011", "value": "{\"int_var\": 4 , \"string_var\": \"cta_4\"}" + }, + { + "id": "170149900112", + "value": "{\"int_var\": 5 , \"string_var\": \"cta_5\"}" } ], "featureEnabled": true