diff --git a/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs b/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs index 7d4ba83..0ee39f0 100644 --- a/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs +++ b/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using DevCycle.SDK.Server.Local.Api; using DevCycle.SDK.Server.Common.Model; @@ -233,6 +233,67 @@ public void Variable_NullUser_ThrowsException() }); } + [TestMethod] + public void Variable_NullKey_ThrowsArgumentException() + { + // Reaching the WASM bucketing engine with a null/empty flag key + // triggers an internal abort() and corrupts the WASM heap. Match + // the Java/Python SDKs (and Cloud client) by failing fast with a + // clear ArgumentException before we ever enter WASM. + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + Assert.Throws(() => api.Variable(user, null, true).Result); + } + + [TestMethod] + public void Variable_EmptyKey_ThrowsArgumentException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + Assert.Throws(() => api.Variable(user, "", true).Result); + } + + [TestMethod] + public async Task VariableAsync_NullKey_ThrowsArgumentException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + await Assert.ThrowsExactlyAsync(async () => + await api.VariableAsync(user, null, true)); + } + + [TestMethod] + public async Task VariableAsync_EmptyKey_ThrowsArgumentException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + await Assert.ThrowsExactlyAsync(async () => + await api.VariableAsync(user, "", true)); + } + + [TestMethod] + public void Variable_NullDefaultValue_ThrowsArgumentNullException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + Assert.Throws(() => api.Variable(user, "some_key", null).Result); + } + + [TestMethod] + public async Task VariableAsync_NullDefaultValue_ThrowsArgumentNullException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + await Assert.ThrowsExactlyAsync(async () => + await api.VariableAsync(user, "some_key", null)); + } + [TestMethod] public void User_NullUserId_ThrowsException() { diff --git a/DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs b/DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs index 01cf24c..8d608c1 100644 --- a/DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs +++ b/DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs @@ -307,6 +307,16 @@ public override Task> Variable(DevCycleUser user, string key, T d { var requestUser = new DevCyclePopulatedUser(user); + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException("key cannot be null or empty"); + } + + if (defaultValue == null) + { + throw new ArgumentNullException(nameof(defaultValue)); + } + if (!configManager.Initialized) { logger.LogWarning("Variable called before DevCycleClient has initialized, returning default value"); @@ -366,6 +376,16 @@ public async Task> VariableAsync(DevCycleUser user, string key, T { var requestUser = new DevCyclePopulatedUser(user); + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException("key cannot be null or empty"); + } + + if (defaultValue == null) + { + throw new ArgumentNullException(nameof(defaultValue)); + } + if (!configManager.Initialized) { logger.LogWarning("Variable called before DevCycleClient has initialized, returning default value");