diff --git a/src/BuildingBlocks/Quota/InMemoryQuotaService.cs b/src/BuildingBlocks/Quota/InMemoryQuotaService.cs index af93a51db1..fd934770fe 100644 --- a/src/BuildingBlocks/Quota/InMemoryQuotaService.cs +++ b/src/BuildingBlocks/Quota/InMemoryQuotaService.cs @@ -18,7 +18,7 @@ public sealed class InMemoryQuotaService : IQuotaService private readonly Dictionary _gauges; private readonly TimeProvider _timeProvider; - internal InMemoryQuotaService( + public InMemoryQuotaService( InMemoryQuotaStore store, QuotaOptions options, QuotaPlanResolver planResolver, diff --git a/src/Tests/Framework.Tests/Quota/QuotaExtensionsTests.cs b/src/Tests/Framework.Tests/Quota/QuotaExtensionsTests.cs new file mode 100644 index 0000000000..033d516063 --- /dev/null +++ b/src/Tests/Framework.Tests/Quota/QuotaExtensionsTests.cs @@ -0,0 +1,61 @@ +using FSH.Framework.Quota; +using FSH.Framework.Shared.Quota; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Framework.Tests.Quota; + +/// +/// Guards the "quota enabled" DI wiring. registers the quota +/// service by type and the enforcement middleware resolves it per request; the host disables +/// ValidateOnBuild, so a service the container cannot construct only fails at resolution time — which +/// is why a non-public constructor turned every authenticated +/// request (login included) into a 500. These tests therefore RESOLVE the service inside a scope +/// (mirroring the middleware), not merely register it. +/// +public sealed class QuotaExtensionsTests +{ + private static ServiceProvider BuildProvider(bool enabled) + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["QuotaOptions:Enabled"] = enabled ? "true" : "false", + }) + .Build(); + + var services = new ServiceCollection(); + services.AddSingleton(configuration); + services.AddLogging(); + services.AddHeroQuotas(configuration); + return services.BuildServiceProvider(); + } + + [Fact] + public void AddHeroQuotas_Should_ResolveInMemoryService_When_EnabledWithoutRedis() + { + // Arrange + using var provider = BuildProvider(enabled: true); + using var scope = provider.CreateScope(); + + // Act — this is the exact resolution the enforcement middleware performs per request. + var quotaService = scope.ServiceProvider.GetRequiredService(); + + // Assert + quotaService.ShouldBeOfType(); + } + + [Fact] + public void AddHeroQuotas_Should_ResolveNoopService_When_Disabled() + { + // Arrange + using var provider = BuildProvider(enabled: false); + using var scope = provider.CreateScope(); + + // Act + var quotaService = scope.ServiceProvider.GetRequiredService(); + + // Assert + quotaService.ShouldBeOfType(); + } +}