From 8dd2b77afec8b269cf919c1915bfd4d6be897887 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Mon, 13 May 2024 14:46:18 -0400 Subject: [PATCH] fixup: add ct specific tests Signed-off-by: Todd Baert --- .../OpenFeatureClientTests.cs | 38 +++++++++++ .../OpenFeature.Tests/OpenFeatureHookTests.cs | 67 +++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/test/OpenFeature.Tests/OpenFeatureClientTests.cs b/test/OpenFeature.Tests/OpenFeatureClientTests.cs index b36947d4..e7c76d75 100644 --- a/test/OpenFeature.Tests/OpenFeatureClientTests.cs +++ b/test/OpenFeature.Tests/OpenFeatureClientTests.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using AutoFixture; using FluentAssertions; @@ -344,6 +345,43 @@ public async Task When_Exception_Occurs_During_Evaluation_Should_Return_Error() _ = featureProviderMock.Received(1).ResolveStructureValueAsync(flagName, defaultValue, Arg.Any()); } + [Fact] + public async Task Cancellation_Token_Added_Is_Passed_To_Provider() + { + var fixture = new Fixture(); + var clientName = fixture.Create(); + var clientVersion = fixture.Create(); + var flagName = fixture.Create(); + var defaultString = fixture.Create(); + var cancelledReason = "cancelled"; + + var cts = new CancellationTokenSource(); + + + var featureProviderMock = Substitute.For(); + featureProviderMock.ResolveStringValueAsync(flagName, defaultString, Arg.Any(), Arg.Any()).Returns(async args => + { + var token = args.ArgAt(3); + while (!token.IsCancellationRequested) + { + await Task.Delay(10); // artificially delay until cancelled + } + return new ResolutionDetails(flagName, defaultString, ErrorType.None, cancelledReason); + }); + featureProviderMock.GetMetadata().Returns(new Metadata(fixture.Create())); + featureProviderMock.GetProviderHooks().Returns(ImmutableList.Empty); + + await Api.Instance.SetProviderAsync(clientName, featureProviderMock); + var client = Api.Instance.GetClient(clientName, clientVersion); + var task = client.GetStringDetailsAsync(flagName, defaultString, EvaluationContext.Empty, null, cts.Token); + cts.Cancel(); // cancel before awaiting + + var response = await task; + response.Value.Should().Be(defaultString); + response.Reason.Should().Be(cancelledReason); + _ = featureProviderMock.Received(1).ResolveStringValueAsync(flagName, defaultString, Arg.Any(), cts.Token); + } + [Fact] public void Should_Get_And_Set_Context() { diff --git a/test/OpenFeature.Tests/OpenFeatureHookTests.cs b/test/OpenFeature.Tests/OpenFeatureHookTests.cs index 0e3effc1..9ca5b364 100644 --- a/test/OpenFeature.Tests/OpenFeatureHookTests.cs +++ b/test/OpenFeature.Tests/OpenFeatureHookTests.cs @@ -3,12 +3,14 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using AutoFixture; using FluentAssertions; using NSubstitute; using NSubstitute.ExceptionExtensions; using OpenFeature.Constant; +using OpenFeature.Error; using OpenFeature.Model; using OpenFeature.Tests.Internal; using Xunit; @@ -554,6 +556,71 @@ public async Task When_Error_Occurs_In_After_Hook_Should_Invoke_Error_Hook() await featureProvider.DidNotReceive().ResolveBooleanValueAsync("test", false, Arg.Any()); } + [Fact] + public async Task Successful_Resolution_Should_Pass_Cancellation_Token() + { + var featureProvider = Substitute.For(); + var hook = Substitute.For(); + var cts = new CancellationTokenSource(); + + featureProvider.GetMetadata().Returns(new Metadata(null)); + featureProvider.GetProviderHooks().Returns(ImmutableList.Empty); + + hook.BeforeAsync(Arg.Any>(), Arg.Any>(), cts.Token).Returns(EvaluationContext.Empty); + featureProvider.ResolveBooleanValueAsync(Arg.Any(), Arg.Any(), Arg.Any(), cts.Token).Returns(new ResolutionDetails("test", false)); + _ = hook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>(), cts.Token); + _ = hook.FinallyAsync(Arg.Any>(), Arg.Any>(), cts.Token); + + await Api.Instance.SetProviderAsync(featureProvider); + var client = Api.Instance.GetClient(); + client.AddHooks(hook); + + await client.GetBooleanValueAsync("test", false, EvaluationContext.Empty, null, cts.Token); + + _ = hook.Received(1).BeforeAsync(Arg.Any>(), Arg.Any>(), cts.Token); + _ = hook.Received(1).AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>(), cts.Token); + _ = hook.Received(1).FinallyAsync(Arg.Any>(), Arg.Any>(), cts.Token); + } + + [Fact] + public async Task Failed_Resolution_Should_Pass_Cancellation_Token() + { + var featureProvider = Substitute.For(); + var hook = Substitute.For(); + var flagOptions = new FlagEvaluationOptions(hook); + var exceptionToThrow = new GeneralException("Fake Exception"); + var cts = new CancellationTokenSource(); + + featureProvider.GetMetadata() + .Returns(new Metadata(null)); + + featureProvider.GetProviderHooks() + .Returns(ImmutableList.Empty); + + hook.BeforeAsync(Arg.Any>(), Arg.Any>()) + .Returns(EvaluationContext.Empty); + + featureProvider.ResolveBooleanValueAsync(Arg.Any(), Arg.Any(), Arg.Any()) + .Throws(exceptionToThrow); + + hook.ErrorAsync(Arg.Any>(), Arg.Any(), Arg.Any>()) + .Returns(new ValueTask()); + + hook.FinallyAsync(Arg.Any>(), Arg.Any>()) + .Returns(new ValueTask()); + + await Api.Instance.SetProviderAsync(featureProvider); + var client = Api.Instance.GetClient(); + + await client.GetBooleanValueAsync("test", true, EvaluationContext.Empty, flagOptions, cts.Token); + + _ = hook.Received(1).BeforeAsync(Arg.Any>(), Arg.Any>(), cts.Token); + _ = hook.Received(1).ErrorAsync(Arg.Any>(), Arg.Any(), Arg.Any>(), cts.Token); + _ = hook.Received(1).FinallyAsync(Arg.Any>(), Arg.Any>(), cts.Token); + + await featureProvider.DidNotReceive().ResolveBooleanValueAsync("test", false, Arg.Any()); + } + [Fact] public void Add_hooks_should_accept_empty_enumerable() {