diff --git a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index e63b3923..dce9010a 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -104,6 +104,7 @@ + diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 490ef7e7..bab40b22 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -29,6 +29,7 @@ using OptimizelySDK.Notifications; using OptimizelySDK.Tests.NotificationTests; using OptimizelySDK.Utils; +using Newtonsoft.Json; namespace OptimizelySDK.Tests { @@ -185,7 +186,31 @@ public void TestValidateInputsInvalidFileJsonValidationSkipped() { string datafile = "{\"name\":\"optimizely\"}"; Optimizely optimizely = new Optimizely(datafile, null, null, null, skipJsonValidation: true); - Assert.IsTrue(optimizely.IsValid); + Assert.IsFalse(optimizely.IsValid); + } + + [Test] + public void TestErrorHandlingWithNullDatafile() + { + var optimizelyNullDatafile = new Optimizely(null, null, LoggerMock.Object, ErrorHandlerMock.Object, null, true); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Unable to parse null datafile."), Times.Once); + ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Unable to parse null datafile.")), Times.Once); + } + + [Test] + public void TestErrorHandlingWithEmptyDatafile() + { + var optimizelyEmptyDatafile = new Optimizely("", null, LoggerMock.Object, ErrorHandlerMock.Object, null, true); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Unable to parse empty datafile."), Times.Once); + ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Unable to parse empty datafile.")), Times.Once); + } + + [Test] + public void TestErrorHandlingWithUnsupportedConfigVersion() + { + var optimizelyUnsupportedVersion = new Optimizely(TestData.UnsupportedVersionDatafile, null, LoggerMock.Object, ErrorHandlerMock.Object, null, true); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, $"This version of the C# SDK does not support the given datafile version: 5"), Times.Once); + ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == $"This version of the C# SDK does not support the given datafile version: 5")), Times.Once); } [Test] diff --git a/OptimizelySDK.Tests/ProjectConfigTest.cs b/OptimizelySDK.Tests/ProjectConfigTest.cs index 8dece0f8..f2b4381e 100644 --- a/OptimizelySDK.Tests/ProjectConfigTest.cs +++ b/OptimizelySDK.Tests/ProjectConfigTest.cs @@ -863,5 +863,32 @@ public void TestGetAttributeIdWithInvalidAttributeKey() Assert.Null(Config.GetAttributeId("invalid_attribute")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"Attribute key ""invalid_attribute"" is not in datafile.")); } + + [Test] + public void TestCreateThrowsWithNullDatafile() + { + var exception = Assert.Throws(() => ProjectConfig.Create(null, null, null)); + Assert.AreEqual("Unable to parse null datafile.", exception.Message); + } + + [Test] + public void TestCreateThrowsWithEmptyDatafile() + { + var exception = Assert.Throws(() => ProjectConfig.Create("", null, null)); + Assert.AreEqual("Unable to parse empty datafile.", exception.Message); + } + + [Test] + public void TestCreateThrowsWithUnsupportedDatafileVersion() + { + var exception = Assert.Throws(() => ProjectConfig.Create(TestData.UnsupportedVersionDatafile, null, null)); + Assert.AreEqual($"This version of the C# SDK does not support the given datafile version: 5", exception.Message); + } + + [Test] + public void TestCreateDoesNotThrowWithValidDatafile() + { + Assert.DoesNotThrow(() => ProjectConfig.Create(TestData.Datafile, null, null)); + } } } diff --git a/OptimizelySDK.Tests/TestData.cs b/OptimizelySDK.Tests/TestData.cs index ab4e6302..0bee0dbf 100644 --- a/OptimizelySDK.Tests/TestData.cs +++ b/OptimizelySDK.Tests/TestData.cs @@ -1,5 +1,5 @@ /* - * Copyright 2017, Optimizely + * Copyright 2017-2018, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ public class TestData { private static string cachedDataFile = null; private static string simpleABExperimentsDatafile = null; + private static string unsupportedVersionDatafile = null; public static string Datafile { @@ -41,7 +42,15 @@ public static string SimpleABExperimentsDatafile return simpleABExperimentsDatafile ?? (simpleABExperimentsDatafile = LoadJsonData("simple_ab_experiments.json")); } } - + + public static string UnsupportedVersionDatafile + { + get + { + return unsupportedVersionDatafile ?? (unsupportedVersionDatafile = LoadJsonData("unsupported_version_datafile.json")); + } + } + private static string LoadJsonData(string fileName = "TestData.json") { var assembly = Assembly.GetExecutingAssembly(); diff --git a/OptimizelySDK.Tests/unsupported_version_datafile.json b/OptimizelySDK.Tests/unsupported_version_datafile.json new file mode 100644 index 00000000..4830b21f --- /dev/null +++ b/OptimizelySDK.Tests/unsupported_version_datafile.json @@ -0,0 +1,42 @@ +{ + "version": "5", + "rollouts": [], + "projectId": "10431130345", + "variables": [], + "featureFlags": [], + "experiments": [{ + "status": "Running", + "key": "ab_running_exp_untargeted", + "layerId": "10417730432", + "trafficAllocation": [{ + "entityId": "10418551353", + "endOfRange": 10000 + }], + "audienceIds": [], + "variations": [{ + "variables": [], + "id": "10418551353", + "key": "all_traffic_variation" + }, + { + "variables": [], + "id": "10418510624", + "key": "no_traffic_variation" + } + ], + "forcedVariations": {}, + "id": "10420810910" + }], + "audiences": [], + "groups": [], + "attributes": [], + "accountId": "10367498574", + "events": [{ + "experimentIds": [ + "10420810910" + ], + "id": "10404198134", + "key": "winning" + }], + "revision": "1337" +} \ No newline at end of file diff --git a/OptimizelySDK/Exceptions/OptimizelyException.cs b/OptimizelySDK/Exceptions/OptimizelyException.cs index a3770aaf..56c7b3ed 100644 --- a/OptimizelySDK/Exceptions/OptimizelyException.cs +++ b/OptimizelySDK/Exceptions/OptimizelyException.cs @@ -105,4 +105,13 @@ public InvalidRolloutException(string message) { } } + + public class ConfigParseException : OptimizelyException + { + public ConfigParseException(string message) + : base(message) + { + + } + } } \ No newline at end of file diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs index 62df0dd3..182cf9e3 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -18,6 +18,7 @@ using OptimizelySDK.ErrorHandler; using OptimizelySDK.Event.Builder; using OptimizelySDK.Event.Dispatcher; +using OptimizelySDK.Exceptions; using OptimizelySDK.Logger; using OptimizelySDK.Utils; using OptimizelySDK.Notifications; @@ -113,7 +114,14 @@ public Optimizely(string datafile, } catch (Exception ex) { - Logger.Log(LogLevel.ERROR, "Provided 'datafile' is in an invalid format. " + ex.Message); + string error = String.Empty; + if (ex.GetType() == typeof(ConfigParseException)) + error = ex.Message; + else + error = "Provided 'datafile' is in an invalid format. " + ex.Message; + + Logger.Log(LogLevel.ERROR, error); + ErrorHandler.HandleError(ex); } } diff --git a/OptimizelySDK/ProjectConfig.cs b/OptimizelySDK/ProjectConfig.cs index 54f5c45a..c6a5c0db 100644 --- a/OptimizelySDK/ProjectConfig.cs +++ b/OptimizelySDK/ProjectConfig.cs @@ -16,6 +16,7 @@ using Newtonsoft.Json; using OptimizelySDK.Entity; using OptimizelySDK.ErrorHandler; +using OptimizelySDK.Exceptions; using OptimizelySDK.Logger; using OptimizelySDK.Utils; using System.Collections.Generic; @@ -25,6 +26,13 @@ namespace OptimizelySDK { public class ProjectConfig { + public enum OPTLYSDKVersion + { + V2 = 2, + V3 = 3, + V4 = 4 + } + public const string RESERVED_ATTRIBUTE_PREFIX = "$opt_"; /// @@ -60,6 +68,14 @@ public class ProjectConfig /// public bool? BotFiltering { get; set; } + + private static List SupportedVersions = new List { + OPTLYSDKVersion.V2, + OPTLYSDKVersion.V3, + OPTLYSDKVersion.V4 + }; + + //========================= Mappings =========================== /// @@ -270,7 +286,7 @@ private void Initialize() public static ProjectConfig Create(string content, ILogger logger, IErrorHandler errorHandler) { - ProjectConfig config = JsonConvert.DeserializeObject(content); + ProjectConfig config = GetConfig(content); config.Logger = logger; config.ErrorHandler = errorHandler; @@ -280,6 +296,21 @@ public static ProjectConfig Create(string content, ILogger logger, IErrorHandler return config; } + private static ProjectConfig GetConfig(string configData) + { + if (configData == null) + throw new ConfigParseException("Unable to parse null datafile."); + + if (string.IsNullOrEmpty(configData)) + throw new ConfigParseException("Unable to parse empty datafile."); + + var config = JsonConvert.DeserializeObject(configData); + + if (SupportedVersions.TrueForAll((supportedVersion) => !(((int)supportedVersion).ToString() == config.Version))) + throw new ConfigParseException(string.Format(@"This version of the C# SDK does not support the given datafile version: {0}", config.Version)); + + return config; + } //========================= Getters ===========================