Skip to content

Commit

Permalink
fixup: add ct specific tests
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
  • Loading branch information
toddbaert committed May 13, 2024
1 parent 8bbf69d commit 036d7a4
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 0 deletions.
38 changes: 38 additions & 0 deletions test/OpenFeature.Tests/OpenFeatureClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -344,6 +345,43 @@ public async Task When_Exception_Occurs_During_Evaluation_Should_Return_Error()
_ = featureProviderMock.Received(1).ResolveStructureValueAsync(flagName, defaultValue, Arg.Any<EvaluationContext>());
}

[Fact]
public async Task Cancellation_Token_Added_Is_Passed_To_Provider()
{
var fixture = new Fixture();
var clientName = fixture.Create<string>();
var clientVersion = fixture.Create<string>();
var flagName = fixture.Create<string>();
var defaultString = fixture.Create<string>();
var cancelledReason = "cancelled";

var cts = new CancellationTokenSource();


var featureProviderMock = Substitute.For<FeatureProvider>();
featureProviderMock.ResolveStringValueAsync(flagName, defaultString, Arg.Any<EvaluationContext>(), Arg.Any<CancellationToken>()).Returns(async args =>
{
var token = args.ArgAt<CancellationToken>(3);
while (!token.IsCancellationRequested)
{
await Task.Delay(10); // artificially delay until cancelled

This comment has been minimized.

Copy link
@toddbaert

toddbaert May 13, 2024

Author Member

Delay forever until cancelled.

}
return new ResolutionDetails<string>(flagName, defaultString, ErrorType.None, cancelledReason);
});
featureProviderMock.GetMetadata().Returns(new Metadata(fixture.Create<string>()));
featureProviderMock.GetProviderHooks().Returns(ImmutableList<Hook>.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

This comment has been minimized.

Copy link
@toddbaert

toddbaert May 13, 2024

Author Member

Here I actually cancel.


var response = await task;
response.Value.Should().Be(defaultString);
response.Reason.Should().Be(cancelledReason);
_ = featureProviderMock.Received(1).ResolveStringValueAsync(flagName, defaultString, Arg.Any<EvaluationContext>(), cts.Token);
}

[Fact]
public void Should_Get_And_Set_Context()
{
Expand Down
67 changes: 67 additions & 0 deletions test/OpenFeature.Tests/OpenFeatureHookTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<EvaluationContext>());
}

[Fact]

This comment has been minimized.

Copy link
@toddbaert

toddbaert May 13, 2024

Author Member

These tests only assert the token is passed throw, I don't do any real cancellation but I think that's sufficient.

public async Task Successful_Resolution_Should_Pass_Cancellation_Token()
{
var featureProvider = Substitute.For<FeatureProvider>();
var hook = Substitute.For<Hook>();
var cts = new CancellationTokenSource();

featureProvider.GetMetadata().Returns(new Metadata(null));
featureProvider.GetProviderHooks().Returns(ImmutableList<Hook>.Empty);

hook.BeforeAsync(Arg.Any<HookContext<bool>>(), Arg.Any<Dictionary<string, object>>(), cts.Token).Returns(EvaluationContext.Empty);
featureProvider.ResolveBooleanValueAsync(Arg.Any<string>(), Arg.Any<bool>(), Arg.Any<EvaluationContext>(), cts.Token).Returns(new ResolutionDetails<bool>("test", false));
_ = hook.AfterAsync(Arg.Any<HookContext<bool>>(), Arg.Any<FlagEvaluationDetails<bool>>(), Arg.Any<Dictionary<string, object>>(), cts.Token);
_ = hook.FinallyAsync(Arg.Any<HookContext<bool>>(), Arg.Any<Dictionary<string, object>>(), 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<HookContext<bool>>(), Arg.Any<Dictionary<string, object>>(), cts.Token);
_ = hook.Received(1).AfterAsync(Arg.Any<HookContext<bool>>(), Arg.Any<FlagEvaluationDetails<bool>>(), Arg.Any<Dictionary<string, object>>(), cts.Token);
_ = hook.Received(1).FinallyAsync(Arg.Any<HookContext<bool>>(), Arg.Any<Dictionary<string, object>>(), cts.Token);
}

[Fact]
public async Task Failed_Resolution_Should_Pass_Cancellation_Token()
{
var featureProvider = Substitute.For<FeatureProvider>();
var hook = Substitute.For<Hook>();
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<Hook>.Empty);

hook.BeforeAsync(Arg.Any<HookContext<bool>>(), Arg.Any<ImmutableDictionary<string, object>>())
.Returns(EvaluationContext.Empty);

featureProvider.ResolveBooleanValueAsync(Arg.Any<string>(), Arg.Any<bool>(), Arg.Any<EvaluationContext>())
.Throws(exceptionToThrow);

hook.ErrorAsync(Arg.Any<HookContext<bool>>(), Arg.Any<Exception>(), Arg.Any<ImmutableDictionary<string, object>>())
.Returns(new ValueTask());

hook.FinallyAsync(Arg.Any<HookContext<bool>>(), Arg.Any<ImmutableDictionary<string, object>>())
.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<HookContext<bool>>(), Arg.Any<ImmutableDictionary<string, object>>(), cts.Token);
_ = hook.Received(1).ErrorAsync(Arg.Any<HookContext<bool>>(), Arg.Any<Exception>(), Arg.Any<ImmutableDictionary<string, object>>(), cts.Token);
_ = hook.Received(1).FinallyAsync(Arg.Any<HookContext<bool>>(), Arg.Any<ImmutableDictionary<string, object>>(), cts.Token);

await featureProvider.DidNotReceive().ResolveBooleanValueAsync("test", false, Arg.Any<EvaluationContext>());
}

[Fact]
public void Add_hooks_should_accept_empty_enumerable()
{
Expand Down

0 comments on commit 036d7a4

Please sign in to comment.