using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Services; using Bit.Core.Utilities; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Xunit; namespace Bit.Core.Test.Utilities; public class RequireFeatureAttributeTests { private const string _testFeature = "test-feature"; [Fact] public void Throws_When_Feature_Disabled() { // Arrange var rfa = new RequireFeatureAttribute(_testFeature); // Act & Assert Assert.Throws<FeatureUnavailableException>(() => rfa.OnActionExecuting(GetContext(enabled: false))); } [Fact] public void Throws_When_Feature_Not_Found() { // Arrange var rfa = new RequireFeatureAttribute("missing-feature"); // Act & Assert Assert.Throws<FeatureUnavailableException>(() => rfa.OnActionExecuting(GetContext(enabled: false))); } [Fact] public void Success_When_Feature_Enabled() { // Arrange var rfa = new RequireFeatureAttribute(_testFeature); // Act rfa.OnActionExecuting(GetContext(enabled: true)); // Assert // The Assert here is NOT throwing an exception } /// <summary> /// Generates a ActionExecutingContext with the necessary services registered to test /// the <see cref="RequireFeatureAttribute"/> /// </summary> /// <param name="enabled">Mock value for the <see cref="_testFeature"/> flag</param> /// <returns></returns> private static ActionExecutingContext GetContext(bool enabled) { IServiceCollection services = new ServiceCollection(); var featureService = Substitute.For<IFeatureService>(); var currentContext = Substitute.For<ICurrentContext>(); featureService.IsEnabled(_testFeature).Returns(enabled); services.AddSingleton(featureService); services.AddSingleton(currentContext); var httpContext = new DefaultHttpContext(); httpContext.RequestServices = services.BuildServiceProvider(); var context = Substitute.For<ActionExecutingContext>( Substitute.For<ActionContext>(httpContext, new RouteData(), Substitute.For<ActionDescriptor>()), new List<IFilterMetadata>(), new Dictionary<string, object>(), Substitute.For<Controller>()); return context; } }