Skip to content

Commit

Permalink
Prepare v9.2.0 release (#91)
Browse files Browse the repository at this point in the history
* Implement missing GetKeyAndValue feature + port test suite from Java SDK

* Adjust benchmarks project to changes

* Bump version
  • Loading branch information
adams85 authored May 13, 2024
1 parent 718440b commit 4325672
Show file tree
Hide file tree
Showing 13 changed files with 560 additions and 13 deletions.
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
environment:
build_version: 9.1.0
build_version: 9.2.0
version: $(build_version)-{build}
image: Visual Studio 2022
configuration: Release
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/NewVersionLib/BenchmarkHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class BasicMatrixTestsDescriptor : IMatrixTestDescriptor
UserCondition = new UserCondition()
{
ComparisonAttribute = nameof(User.Identifier),
Comparator = UserComparator.SensitiveIsOneOf,
Comparator = UserComparator.SensitiveTextIsOneOf,
StringListValue = new[]
{
"61418c941ecda8031d08ab86ec821e676fde7b6a59cd16b1e7191503c2f8297d",
Expand All @@ -60,7 +60,7 @@ public class BasicMatrixTestsDescriptor : IMatrixTestDescriptor
UserCondition = new UserCondition()
{
ComparisonAttribute = nameof(User.Email),
Comparator = UserComparator.ContainsAnyOf,
Comparator = UserComparator.TextContainsAnyOf,
StringListValue = new[] { "@example.com" }
}
},
Expand Down
2 changes: 1 addition & 1 deletion src/ConfigCat.Client.Tests/EvaluationTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void GetValue_WithUser_ShouldReturnEvaluatedValue()
private delegate EvaluationDetails<object> EvaluateDelegate(IRolloutEvaluator evaluator, Dictionary<string, Setting> settings, string key, object defaultValue, User user,
ProjectConfig remoteConfig, LoggerWrapper logger);

private static readonly MethodInfo EvaluateMethodDefinition = new EvaluateDelegate(RolloutEvaluatorExtensions.Evaluate).Method.GetGenericMethodDefinition();
private static readonly MethodInfo EvaluateMethodDefinition = new EvaluateDelegate(EvaluationHelper.Evaluate).Method.GetGenericMethodDefinition();

[DataRow("stringDefaultCat", "", "Cat", typeof(string))]
[DataRow("stringDefaultCat", "", "Cat", typeof(object))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ internal override Config FetchConfig()
using Stream stream = File.OpenRead(FilePath);
using StreamReader reader = new(stream);
var configJson = reader.ReadToEnd();
#if BENCHMARK_OLD
return configJson.Deserialize<Config>() ?? throw new InvalidOperationException("Invalid config JSON content: " + configJson);
#else
return Config.Deserialize(configJson.AsMemory());
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public void LazyLoadDeadLockCheck()
["GetValueAsync"] = new object?[] { "x", null, null, CancellationToken.None },
["GetValueDetails"] = new object?[] { "x", null, null },
["GetValueDetailsAsync"] = new object?[] { "x", null, null, CancellationToken.None },
["GetKeyAndValue"] = new object?[] { "x" },
["GetKeyAndValueAsync"] = new object?[] { "x", CancellationToken.None },
["SetDefaultUser"] = new object?[] { new User("id") },
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace ConfigCat.Client.Tests;

[TestClass]
public class UtilsTest
public class UtilsTests
{
[DataRow(new byte[] { }, "")]
[DataRow(new byte[] { 0 }, "00")]
Expand Down
300 changes: 300 additions & 0 deletions src/ConfigCat.Client.Tests/VariationIdTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using ConfigCat.Client.ConfigService;
using ConfigCat.Client.Evaluation;
using ConfigCat.Client.Tests.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace ConfigCat.Client.Tests;

[TestClass]
public class VariationIdTests
{
private const string TestJson = "{ \"p\":{ \"u\": \"https://cdn-global.configcat.com\", \"r\": 0, \"s\": \"test-salt\"}, \"f\":{ \"key1\":{ \"t\":0, \"r\":[ { \"c\":[ { \"u\":{ \"a\": \"Email\", \"c\": 2 , \"l \":[ \"@configcat.com\" ] } } ], \"s\":{ \"v\": { \"b\":true }, \"i\": \"rolloutId1\" } }, { \"c\": [ { \"u\" :{ \"a\": \"Email\", \"c\": 2, \"l\" : [ \"@test.com\" ] } } ], \"s\" : { \"v\" : { \"b\": false }, \"i\": \"rolloutId2\" } } ], \"p\":[ { \"p\":50, \"v\" : { \"b\": true }, \"i\" : \"percentageId1\" }, { \"p\" : 50, \"v\" : { \"b\": false }, \"i\": \"percentageId2\" } ], \"v\":{ \"b\":true }, \"i\": \"fakeId1\" }, \"key2\": { \"t\":0, \"v\": { \"b\": false }, \"i\": \"fakeId2\" }, \"key3\": { \"t\": 0, \"r\":[ { \"c\": [ { \"u\":{ \"a\": \"Email\", \"c\":2, \"l\":[ \"@configcat.com\" ] } } ], \"p\": [{ \"p\":50, \"v\":{ \"b\": true }, \"i\" : \"targetPercentageId1\" }, { \"p\": 50, \"v\": { \"b\":false }, \"i\" : \"targetPercentageId2\" } ] } ], \"v\":{ \"b\": false }, \"i\": \"fakeId3\" } } }";
private const string TestJsonIncorrect = "{ \"p\":{ \"u\": \"https://cdn-global.configcat.com\", \"r\": 0, \"s\": \"test-salt\" }, \"f\" :{ \"incorrect\" : { \"t\": 0, \"r\": [ {\"c\": [ {\"u\": {\"a\": \"Email\", \"c\": 2, \"l\": [\"@configcat.com\"] } } ] } ],\"v\": {\"b\": false}, \"i\": \"incorrectId\" } } }";

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetVariationId_Works(bool? isAsync)
{
using var client = CreateClient(isAsync, TestJson);

const string key = "key1";
var valueDetails =
isAsync is null ? client.Snapshot().GetValueDetails<bool?>(key, null) :
!isAsync.Value ? client.GetValueDetails<bool?>(key, null) :
await client.GetValueDetailsAsync<bool?>(key, null);
Assert.AreEqual("fakeId1", valueDetails.VariationId);
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetVariationId_NotFound(bool? isAsync)
{
using var client = CreateClient(isAsync, TestJson);

const string key = "nonexisting";
var valueDetails =
isAsync is null ? client.Snapshot().GetValueDetails<bool?>(key, null) :
!isAsync.Value ? client.GetValueDetails<bool?>(key, null) :
await client.GetValueDetailsAsync<bool?>(key, null);
Assert.IsNull(valueDetails.VariationId);
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetAllVariationIds_Works(bool? isAsync)
{
using var client = CreateClient(isAsync, TestJson);

ConfigCatClientSnapshot snapshot;
EvaluationDetails[] allValueDetails =
isAsync is null ? (snapshot = client.Snapshot()).GetAllKeys().Select(keys => snapshot.GetValueDetails<object?>(keys, null)).ToArray() :
!isAsync.Value ? client.GetAllValueDetails().ToArray() :
(await client.GetAllValueDetailsAsync()).ToArray();

Assert.AreEqual(3, allValueDetails.Length);

Array.Sort(allValueDetails, (x, y) => StringComparer.Ordinal.Compare(x.Key, y.Key));
Assert.AreEqual("fakeId1", allValueDetails[0].VariationId);
Assert.AreEqual("fakeId2", allValueDetails[1].VariationId);
Assert.AreEqual("fakeId3", allValueDetails[2].VariationId);
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetAllVariationIds_Works_Empty(bool? isAsync)
{
using var client = CreateClient(isAsync, "{}");

ConfigCatClientSnapshot snapshot;
EvaluationDetails[] allValueDetails =
isAsync is null ? (snapshot = client.Snapshot()).GetAllKeys().Select(keys => snapshot.GetValueDetails<object?>(keys, null)).ToArray() :
!isAsync.Value ? client.GetAllValueDetails().ToArray() :
(await client.GetAllValueDetailsAsync()).ToArray();

Assert.AreEqual(0, allValueDetails.Length);
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetKeyAndValue_Works(bool? isAsync)
{
using var client = CreateClient(isAsync, TestJson);

async Task<KeyValuePair<string, bool>?> GetKeyAndValue(string variationId)
{
return
isAsync is null ? client.Snapshot().GetKeyAndValue<bool>(variationId) :
!isAsync.Value ? client.GetKeyAndValue<bool>(variationId) :
await client.GetKeyAndValueAsync<bool>(variationId);
}

var result = await GetKeyAndValue("fakeId2");
Assert.IsNotNull(result);
Assert.AreEqual("key2", result.Value.Key);
Assert.IsFalse(result.Value.Value);

result = await GetKeyAndValue("percentageId2");
Assert.IsNotNull(result);
Assert.AreEqual("key1", result.Value.Key);
Assert.IsFalse(result.Value.Value);

result = await GetKeyAndValue("rolloutId1");
Assert.IsNotNull(result);
Assert.AreEqual("key1", result.Value.Key);
Assert.IsTrue(result.Value.Value);

result = await GetKeyAndValue("targetPercentageId2");
Assert.IsNotNull(result);
Assert.AreEqual("key3", result.Value.Key);
Assert.IsFalse(result.Value.Value);
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetKeyAndValue_Works_Nullable(bool? isAsync)
{
using var client = CreateClient(isAsync, TestJson);

async Task<KeyValuePair<string, bool?>?> GetKeyAndValue(string variationId)
{
return
isAsync is null ? client.Snapshot().GetKeyAndValue<bool?>(variationId) :
!isAsync.Value ? client.GetKeyAndValue<bool?>(variationId) :
await client.GetKeyAndValueAsync<bool?>(variationId);
}

var result = await GetKeyAndValue("fakeId2");
Assert.IsNotNull(result);
Assert.AreEqual("key2", result.Value.Key);
Assert.IsFalse(result.Value.Value);

result = await GetKeyAndValue("percentageId2");
Assert.IsNotNull(result);
Assert.AreEqual("key1", result.Value.Key);
Assert.IsFalse(result.Value.Value);

result = await GetKeyAndValue("rolloutId1");
Assert.IsNotNull(result);
Assert.AreEqual("key1", result.Value.Key);
Assert.IsTrue(result.Value.Value);

result = await GetKeyAndValue("targetPercentageId2");
Assert.IsNotNull(result);
Assert.AreEqual("key3", result.Value.Key);
Assert.IsFalse(result.Value.Value);
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetKeyAndValue_Works_Object(bool? isAsync)
{
using var client = CreateClient(isAsync, TestJson);

async Task<KeyValuePair<string, object>?> GetKeyAndValue(string variationId)
{
return
isAsync is null ? client.Snapshot().GetKeyAndValue<object>(variationId) :
!isAsync.Value ? client.GetKeyAndValue<object>(variationId) :
await client.GetKeyAndValueAsync<object>(variationId);
}

var result = await GetKeyAndValue("fakeId2");
Assert.IsNotNull(result);
Assert.AreEqual("key2", result.Value.Key);
Assert.AreEqual(false, result.Value.Value);

result = await GetKeyAndValue("percentageId2");
Assert.IsNotNull(result);
Assert.AreEqual("key1", result.Value.Key);
Assert.AreEqual(false, result.Value.Value);

result = await GetKeyAndValue("rolloutId1");
Assert.IsNotNull(result);
Assert.AreEqual("key1", result.Value.Key);
Assert.AreEqual(true, result.Value.Value);

result = await GetKeyAndValue("targetPercentageId2");
Assert.IsNotNull(result);
Assert.AreEqual("key3", result.Value.Key);
Assert.AreEqual(false, result.Value.Value);
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetKeyAndValue_NotFound(bool? isAsync)
{
var logEvents = new List<LogEvent>();
var logger = LoggingHelper.CreateCapturingLogger(logEvents);

using var client = CreateClient(isAsync, TestJson, logger);

const string variationId = "nonexisting";
var result =
isAsync is null ? client.Snapshot().GetKeyAndValue<bool>(variationId) :
!isAsync.Value ? client.GetKeyAndValue<bool>(variationId) :
await client.GetKeyAndValueAsync<bool>(variationId);

Assert.IsNull(result);

Assert.AreEqual(1, logEvents.Count);
Assert.AreEqual(2011, logEvents[0].EventId);
Assert.IsNull(logEvents[0].Exception);
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetKeyAndValue_TypeMismatch(bool? isAsync)
{
var logEvents = new List<LogEvent>();
var logger = LoggingHelper.CreateCapturingLogger(logEvents);

using var client = CreateClient(isAsync, TestJson, logger);

const string variationId = "fakeId2";
var result =
isAsync is null ? client.Snapshot().GetKeyAndValue<string>(variationId) :
!isAsync.Value ? client.GetKeyAndValue<string>(variationId) :
await client.GetKeyAndValueAsync<string>(variationId);

Assert.IsNull(result);

Assert.AreEqual(1, logEvents.Count);
Assert.AreEqual(1002, logEvents[0].EventId);
Assert.IsNotNull(logEvents[0].Exception);
StringAssert.Contains(logEvents[0].Exception!.Message, "is not of the expected type");
}

[DataTestMethod]
[DataRow(null)]
[DataRow(false)]
[DataRow(true)]
public async Task GetKeyAndValue_IncorrectTargetingRule(bool? isAsync)
{
var logEvents = new List<LogEvent>();
var logger = LoggingHelper.CreateCapturingLogger(logEvents);

using var client = CreateClient(isAsync, TestJsonIncorrect, logger);

const string variationId = "targetPercentageId2";
var result =
isAsync is null ? client.Snapshot().GetKeyAndValue<bool>(variationId) :
!isAsync.Value ? client.GetKeyAndValue<bool>(variationId) :
await client.GetKeyAndValueAsync<bool>(variationId);

Assert.IsNull(result);

Assert.AreEqual(1, logEvents.Count);
Assert.AreEqual(1002, logEvents[0].EventId);
Assert.IsNotNull(logEvents[0].Exception);
StringAssert.Contains(logEvents[0].Exception!.Message, "THEN part is missing or invalid");
}

private static ConfigCatClient CreateClient(bool? isAsync, string configJson, IConfigCatLogger? logger = null)
{
logger ??= new Mock<IConfigCatLogger>().Object;

var evaluator = new RolloutEvaluator(logger.AsWrapper());
var configServiceMock = new Mock<IConfigService>();

var config = ConfigHelper.FromString(configJson, "\"123\"", DateTime.UtcNow);
if (isAsync is null)
{
configServiceMock.Setup(m => m.GetInMemoryConfig()).Returns(config);
}
else if (!isAsync.Value)
{
configServiceMock.Setup(m => m.GetConfig()).Returns(config);
}
else
{
configServiceMock.Setup(m => m.GetConfigAsync(It.IsAny<CancellationToken>())).ReturnsAsync(config);
}

return new ConfigCatClient(configServiceMock.Object, logger, evaluator);
}
}
Loading

0 comments on commit 4325672

Please sign in to comment.