diff --git a/src/OpenFeature/Api.cs b/src/OpenFeature/Api.cs index 33a7c79d..3532e99e 100644 --- a/src/OpenFeature/Api.cs +++ b/src/OpenFeature/Api.cs @@ -40,12 +40,39 @@ internal Api() { } /// /// The provider cannot be set to null. Attempting to set the provider to null has no effect. May throw an exception if cannot be initialized. /// Implementation of - public async Task SetProviderAsync(FeatureProvider featureProvider) + /// A that completes once Provider initialization is complete. + public Task SetProviderAsync(FeatureProvider featureProvider) + { + return this.SetProviderAsync(featureProvider, CancellationToken.None); + } + + /// + /// Sets the default feature provider. In order to wait for the provider to be set, and initialization to complete, + /// await the returned task. + /// + /// The provider cannot be set to null. Attempting to set the provider to null has no effect. May throw an exception if cannot be initialized. + /// Implementation of + /// Propagates notification that the provider initialization should be canceled. + /// A that completes once Provider initialization is complete. + public async Task SetProviderAsync(FeatureProvider featureProvider, CancellationToken cancellationToken) { this._eventExecutor.RegisterDefaultFeatureProvider(featureProvider); - await this._repository.SetProviderAsync(featureProvider, this.GetContext(), this.AfterInitializationAsync, this.AfterErrorAsync) + await this._repository.SetProviderAsync(featureProvider, this.GetContext(), this.AfterInitializationAsync, this.AfterErrorAsync, cancellationToken) .ConfigureAwait(false); + } + /// + /// Binds the feature provider to the given domain. In order to wait for the provider to be set, and + /// initialization to complete, await the returned task. + /// + /// The provider cannot be set to null. Attempting to set the provider to null has no effect. May throw an exception if cannot be initialized. + /// An identifier which logically binds clients with providers + /// Implementation of + /// domain cannot be null or empty + /// A that completes once Provider initialization is complete. + public Task SetProviderAsync(string domain, FeatureProvider featureProvider) + { + return this.SetProviderAsync(domain, featureProvider, CancellationToken.None); } /// @@ -55,15 +82,17 @@ await this._repository.SetProviderAsync(featureProvider, this.GetContext(), this /// The provider cannot be set to null. Attempting to set the provider to null has no effect. May throw an exception if cannot be initialized. /// An identifier which logically binds clients with providers /// Implementation of + /// Propagates notification that the provider initialization should be canceled. /// domain cannot be null or empty - public async Task SetProviderAsync(string domain, FeatureProvider featureProvider) + /// A that completes once Provider initialization is complete. + public async Task SetProviderAsync(string domain, FeatureProvider featureProvider, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(domain)) { throw new ArgumentNullException(nameof(domain)); } this._eventExecutor.RegisterClientFeatureProvider(domain, featureProvider); - await this._repository.SetProviderAsync(domain, featureProvider, this.GetContext(), this.AfterInitializationAsync, this.AfterErrorAsync) + await this._repository.SetProviderAsync(domain, featureProvider, this.GetContext(), this.AfterInitializationAsync, this.AfterErrorAsync, cancellationToken) .ConfigureAwait(false); } diff --git a/test/OpenFeature.Tests/OpenFeatureTests.cs b/test/OpenFeature.Tests/OpenFeatureTests.cs index 9eb0aa40..835406ef 100644 --- a/test/OpenFeature.Tests/OpenFeatureTests.cs +++ b/test/OpenFeature.Tests/OpenFeatureTests.cs @@ -34,6 +34,43 @@ public async Task OpenFeature_Should_Initialize_Provider() await providerMockNamed.Received(1).InitializeAsync(Api.Instance.GetContext()); } + [Fact] + public async Task OpenFeature_Should_Initialize_Provider_WithCancellationToken() + { + var providerMockDefault = Substitute.For(); + providerMockDefault.Status.Returns(ProviderStatus.NotReady); + + using var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; + + await Api.Instance.SetProviderAsync(providerMockDefault, cancellationToken); + await providerMockDefault.Received(1).InitializeAsync(Api.Instance.GetContext(), cancellationToken); + + var providerMockNamed = Substitute.For(); + providerMockNamed.Status.Returns(ProviderStatus.NotReady); + + await Api.Instance.SetProviderAsync("the-name", providerMockNamed, cancellationToken); + await providerMockNamed.Received(1).InitializeAsync(Api.Instance.GetContext(), cancellationToken); + } + + [Fact] + public async Task OpenFeature_Should_Handle_Cancellation_During_Initialization() + { + using var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + var cancellationToken = cancellationTokenSource.Token; + + var providerMockDefault = Substitute.For(); + providerMockDefault.InitializeAsync(Arg.Any(), cancellationToken) + .Returns(ci => Task.FromCanceled(cancellationToken)); + + await Assert.ThrowsAsync(() => + Api.Instance.SetProviderAsync(providerMockDefault, cancellationToken)); + + await providerMockDefault.Received(1).InitializeAsync(Api.Instance.GetContext(), cancellationToken); + Assert.Equal(ProviderStatus.Error, providerMockDefault.Status); + } + [Fact] [Specification("1.1.2.3", "The provider mutator function MUST invoke the shutdown function on the previously registered provider once it's no longer being used to resolve flag values.")]