Browse Source

Rename Volo.Abp.OperationRateLimit to Volo.Abp.OperationRateLimiting

pull/25024/head
maliming 7 months ago
parent
commit
b92eda2419
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 4
      framework/Volo.Abp.slnx
  2. 8
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/AbpOperationRateLimitResource.cs
  3. 14
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitChecker.cs
  4. 8
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitFormatter.cs
  5. 11
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitPolicyProvider.cs
  6. 12
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitRule.cs
  7. 13
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitStore.cs
  8. 16
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitRuleDefinition.cs
  9. 0
      framework/src/Volo.Abp.OperationRateLimiting/FodyWeavers.xml
  10. 8
      framework/src/Volo.Abp.OperationRateLimiting/Volo.Abp.OperationRateLimiting.csproj
  11. 6
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingErrorCodes.cs
  12. 14
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingModule.cs
  13. 10
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingOptions.cs
  14. 8
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingResource.cs
  15. 14
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/IOperationRateLimitingChecker.cs
  16. 64
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingChecker.cs
  17. 6
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingContext.cs
  18. 6
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingResult.cs
  19. 4
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingRuleResult.cs
  20. 12
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Exceptions/AbpOperationRateLimitingException.cs
  21. 12
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Formatting/DefaultOperationRateLimitingFormatter.cs
  22. 8
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Formatting/IOperationRateLimitingFormatter.cs
  23. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/ar.json
  24. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/cs.json
  25. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/de.json
  26. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/el.json
  27. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/en-GB.json
  28. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/en.json
  29. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/es.json
  30. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/fa.json
  31. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/fi.json
  32. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/fr.json
  33. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/hi.json
  34. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/hr.json
  35. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/hu.json
  36. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/is.json
  37. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/it.json
  38. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/nl.json
  39. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/pl-PL.json
  40. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/pt-BR.json
  41. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/ro-RO.json
  42. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/ru.json
  43. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/sk.json
  44. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/sl.json
  45. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/sv.json
  46. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/tr.json
  47. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/vi.json
  48. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/zh-Hans.json
  49. 14
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/DefaultOperationRateLimitingPolicyProvider.cs
  50. 11
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/IOperationRateLimitingPolicyProvider.cs
  51. 4
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingPartitionType.cs
  52. 6
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingPolicy.cs
  53. 34
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingPolicyBuilder.cs
  54. 52
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleBuilder.cs
  55. 16
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleDefinition.cs
  56. 46
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs
  57. 12
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/IOperationRateLimitingRule.cs
  58. 38
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Store/DistributedCacheOperationRateLimitingStore.cs
  59. 13
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Store/IOperationRateLimitingStore.cs
  60. 6
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Store/OperationRateLimitingCacheItem.cs
  61. 4
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Store/OperationRateLimitingStoreResult.cs
  62. 2
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo.Abp.OperationRateLimiting.Tests.csproj
  63. 26
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingException_Tests.cs
  64. 20
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingPhase2RaceTestModule.cs
  65. 8
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingTestModule.cs
  66. 10
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/DistributedCacheOperationRateLimitingStore_Tests.cs
  67. 36
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingCheckerFixes_Tests.cs
  68. 130
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingChecker_Tests.cs
  69. 38
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingFrontendIntegration_Tests.cs
  70. 28
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingMultiTenant_Tests.cs
  71. 54
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingPolicyBuilder_Tests.cs
  72. 4
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingTestBase.cs

4
framework/Volo.Abp.slnx

@ -169,7 +169,7 @@
<Project Path="src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj" /> <Project Path="src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj" />
<Project Path="src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj" /> <Project Path="src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj" />
<Project Path="src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj" /> <Project Path="src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj" />
<Project Path="src/Volo.Abp.OperationRateLimit/Volo.Abp.OperationRateLimit.csproj" /> <Project Path="src/Volo.Abp.OperationRateLimiting/Volo.Abp.OperationRateLimiting.csproj" />
</Folder> </Folder>
<Folder Name="/test/"> <Folder Name="/test/">
<Project Path="test/AbpTestBase/AbpTestBase.csproj" /> <Project Path="test/AbpTestBase/AbpTestBase.csproj" />
@ -257,6 +257,6 @@
<Project Path="test/Volo.Abp.Uow.Tests/Volo.Abp.Uow.Tests.csproj" /> <Project Path="test/Volo.Abp.Uow.Tests/Volo.Abp.Uow.Tests.csproj" />
<Project Path="test/Volo.Abp.Validation.Tests/Volo.Abp.Validation.Tests.csproj" /> <Project Path="test/Volo.Abp.Validation.Tests/Volo.Abp.Validation.Tests.csproj" />
<Project Path="test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj" /> <Project Path="test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj" />
<Project Path="test/Volo.Abp.OperationRateLimit.Tests/Volo.Abp.OperationRateLimit.Tests.csproj" /> <Project Path="test/Volo.Abp.OperationRateLimiting.Tests/Volo.Abp.OperationRateLimiting.Tests.csproj" />
</Folder> </Folder>
</Solution> </Solution>

8
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/AbpOperationRateLimitResource.cs

@ -1,8 +0,0 @@
using Volo.Abp.Localization;
namespace Volo.Abp.OperationRateLimit;
[LocalizationResourceName("AbpOperationRateLimit")]
public class AbpOperationRateLimitResource
{
}

14
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitChecker.cs

@ -1,14 +0,0 @@
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimit;
public interface IOperationRateLimitChecker
{
Task CheckAsync(string policyName, OperationRateLimitContext? context = null);
Task<bool> IsAllowedAsync(string policyName, OperationRateLimitContext? context = null);
Task<OperationRateLimitResult> GetStatusAsync(string policyName, OperationRateLimitContext? context = null);
Task ResetAsync(string policyName, OperationRateLimitContext? context = null);
}

8
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitFormatter.cs

@ -1,8 +0,0 @@
using System;
namespace Volo.Abp.OperationRateLimit;
public interface IOperationRateLimitFormatter
{
string Format(TimeSpan duration);
}

11
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitPolicyProvider.cs

@ -1,11 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimit;
public interface IOperationRateLimitPolicyProvider
{
Task<OperationRateLimitPolicy> GetAsync(string policyName);
Task<List<OperationRateLimitPolicy>> GetListAsync();
}

12
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitRule.cs

@ -1,12 +0,0 @@
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimit;
public interface IOperationRateLimitRule
{
Task<OperationRateLimitRuleResult> AcquireAsync(OperationRateLimitContext context);
Task<OperationRateLimitRuleResult> CheckAsync(OperationRateLimitContext context);
Task ResetAsync(OperationRateLimitContext context);
}

13
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/IOperationRateLimitStore.cs

@ -1,13 +0,0 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimit;
public interface IOperationRateLimitStore
{
Task<OperationRateLimitStoreResult> IncrementAsync(string key, TimeSpan duration, int maxCount);
Task<OperationRateLimitStoreResult> GetAsync(string key, TimeSpan duration, int maxCount);
Task ResetAsync(string key);
}

16
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitRuleDefinition.cs

@ -1,16 +0,0 @@
using System;
namespace Volo.Abp.OperationRateLimit;
public class OperationRateLimitRuleDefinition
{
public TimeSpan Duration { get; set; }
public int MaxCount { get; set; }
public OperationRateLimitPartitionType PartitionType { get; set; }
public Func<OperationRateLimitContext, string>? CustomPartitionKeyResolver { get; set; }
public bool IsMultiTenant { get; set; }
}

0
framework/src/Volo.Abp.OperationRateLimit/FodyWeavers.xml → framework/src/Volo.Abp.OperationRateLimiting/FodyWeavers.xml

8
framework/src/Volo.Abp.OperationRateLimit/Volo.Abp.OperationRateLimit.csproj → framework/src/Volo.Abp.OperationRateLimiting/Volo.Abp.OperationRateLimiting.csproj

@ -7,8 +7,8 @@
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors> <WarningsAsErrors>Nullable</WarningsAsErrors>
<AssemblyName>Volo.Abp.OperationRateLimit</AssemblyName> <AssemblyName>Volo.Abp.OperationRateLimiting</AssemblyName>
<PackageId>Volo.Abp.OperationRateLimit</PackageId> <PackageId>Volo.Abp.OperationRateLimiting</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> <AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
@ -17,8 +17,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="Volo\Abp\OperationRateLimit\Localization\*.json" /> <None Remove="Volo\Abp\OperationRateLimiting\Localization\*.json" />
<EmbeddedResource Include="Volo\Abp\OperationRateLimit\Localization\*.json" /> <EmbeddedResource Include="Volo\Abp\OperationRateLimiting\Localization\*.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

6
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/AbpOperationRateLimitErrorCodes.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingErrorCodes.cs

@ -1,9 +1,9 @@
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public static class AbpOperationRateLimitErrorCodes public static class AbpOperationRateLimitingErrorCodes
{ {
/// <summary> /// <summary>
/// Default error code for rate limit exceeded. /// Default error code for rate limit exceeded.
/// </summary> /// </summary>
public const string ExceedLimit = "Volo.Abp.OperationRateLimit:010001"; public const string ExceedLimit = "Volo.Abp.OperationRateLimiting:010001";
} }

14
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/AbpOperationRateLimitModule.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingModule.cs

@ -7,7 +7,7 @@ using Volo.Abp.Modularity;
using Volo.Abp.Security; using Volo.Abp.Security;
using Volo.Abp.VirtualFileSystem; using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
[DependsOn( [DependsOn(
typeof(AbpCachingModule), typeof(AbpCachingModule),
@ -16,27 +16,27 @@ namespace Volo.Abp.OperationRateLimit;
typeof(AbpAspNetCoreAbstractionsModule), typeof(AbpAspNetCoreAbstractionsModule),
typeof(AbpDistributedLockingAbstractionsModule) typeof(AbpDistributedLockingAbstractionsModule)
)] )]
public class AbpOperationRateLimitModule : AbpModule public class AbpOperationRateLimitingModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<AbpVirtualFileSystemOptions>(options => Configure<AbpVirtualFileSystemOptions>(options =>
{ {
options.FileSets.AddEmbedded<AbpOperationRateLimitModule>(); options.FileSets.AddEmbedded<AbpOperationRateLimitingModule>();
}); });
Configure<AbpLocalizationOptions>(options => Configure<AbpLocalizationOptions>(options =>
{ {
options.Resources options.Resources
.Add<AbpOperationRateLimitResource>("en") .Add<AbpOperationRateLimitingResource>("en")
.AddVirtualJson("/Volo/Abp/OperationRateLimit/Localization"); .AddVirtualJson("/Volo/Abp/OperationRateLimiting/Localization");
}); });
Configure<AbpExceptionLocalizationOptions>(options => Configure<AbpExceptionLocalizationOptions>(options =>
{ {
options.MapCodeNamespace( options.MapCodeNamespace(
"Volo.Abp.OperationRateLimit", "Volo.Abp.OperationRateLimiting",
typeof(AbpOperationRateLimitResource)); typeof(AbpOperationRateLimitingResource));
}); });
} }
} }

10
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/AbpOperationRateLimitOptions.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingOptions.cs

@ -1,19 +1,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class AbpOperationRateLimitOptions public class AbpOperationRateLimitingOptions
{ {
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;
public TimeSpan LockTimeout { get; set; } = TimeSpan.FromSeconds(5); public TimeSpan LockTimeout { get; set; } = TimeSpan.FromSeconds(5);
public Dictionary<string, OperationRateLimitPolicy> Policies { get; } = new(); public Dictionary<string, OperationRateLimitingPolicy> Policies { get; } = new();
public void AddPolicy(string name, Action<OperationRateLimitPolicyBuilder> configure) public void AddPolicy(string name, Action<OperationRateLimitingPolicyBuilder> configure)
{ {
var builder = new OperationRateLimitPolicyBuilder(name); var builder = new OperationRateLimitingPolicyBuilder(name);
configure(builder); configure(builder);
Policies[name] = builder.Build(); Policies[name] = builder.Build();
} }

8
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingResource.cs

@ -0,0 +1,8 @@
using Volo.Abp.Localization;
namespace Volo.Abp.OperationRateLimiting;
[LocalizationResourceName("AbpOperationRateLimiting")]
public class AbpOperationRateLimitingResource
{
}

14
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/IOperationRateLimitingChecker.cs

@ -0,0 +1,14 @@
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimiting;
public interface IOperationRateLimitingChecker
{
Task CheckAsync(string policyName, OperationRateLimitingContext? context = null);
Task<bool> IsAllowedAsync(string policyName, OperationRateLimitingContext? context = null);
Task<OperationRateLimitingResult> GetStatusAsync(string policyName, OperationRateLimitingContext? context = null);
Task ResetAsync(string policyName, OperationRateLimitingContext? context = null);
}

64
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitChecker.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingChecker.cs

@ -9,23 +9,23 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Users; using Volo.Abp.Users;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientDependency public class OperationRateLimitingChecker : IOperationRateLimitingChecker, ITransientDependency
{ {
protected AbpOperationRateLimitOptions Options { get; } protected AbpOperationRateLimitingOptions Options { get; }
protected IOperationRateLimitPolicyProvider PolicyProvider { get; } protected IOperationRateLimitingPolicyProvider PolicyProvider { get; }
protected IServiceProvider ServiceProvider { get; } protected IServiceProvider ServiceProvider { get; }
protected IOperationRateLimitStore Store { get; } protected IOperationRateLimitingStore Store { get; }
protected ICurrentUser CurrentUser { get; } protected ICurrentUser CurrentUser { get; }
protected ICurrentTenant CurrentTenant { get; } protected ICurrentTenant CurrentTenant { get; }
protected IWebClientInfoProvider WebClientInfoProvider { get; } protected IWebClientInfoProvider WebClientInfoProvider { get; }
public OperationRateLimitChecker( public OperationRateLimitingChecker(
IOptions<AbpOperationRateLimitOptions> options, IOptions<AbpOperationRateLimitingOptions> options,
IOperationRateLimitPolicyProvider policyProvider, IOperationRateLimitingPolicyProvider policyProvider,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IOperationRateLimitStore store, IOperationRateLimitingStore store,
ICurrentUser currentUser, ICurrentUser currentUser,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IWebClientInfoProvider webClientInfoProvider) IWebClientInfoProvider webClientInfoProvider)
@ -39,7 +39,7 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
WebClientInfoProvider = webClientInfoProvider; WebClientInfoProvider = webClientInfoProvider;
} }
public virtual async Task CheckAsync(string policyName, OperationRateLimitContext? context = null) public virtual async Task CheckAsync(string policyName, OperationRateLimitingContext? context = null)
{ {
if (!Options.IsEnabled) if (!Options.IsEnabled)
{ {
@ -52,7 +52,7 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
// Phase 1: Check ALL rules without incrementing to get complete status. // Phase 1: Check ALL rules without incrementing to get complete status.
// Do not exit early: a later rule may have a larger RetryAfter that the caller needs to know about. // Do not exit early: a later rule may have a larger RetryAfter that the caller needs to know about.
var checkResults = new List<OperationRateLimitRuleResult>(); var checkResults = new List<OperationRateLimitingRuleResult>();
foreach (var rule in rules) foreach (var rule in rules)
{ {
checkResults.Add(await rule.CheckAsync(context)); checkResults.Add(await rule.CheckAsync(context));
@ -68,7 +68,7 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
// Phase 2: All rules pass - now increment all counters. // Phase 2: All rules pass - now increment all counters.
// Also guard against a concurrent race where another request consumed the last quota // Also guard against a concurrent race where another request consumed the last quota
// between Phase 1 and Phase 2. // between Phase 1 and Phase 2.
var incrementResults = new List<OperationRateLimitRuleResult>(); var incrementResults = new List<OperationRateLimitingRuleResult>();
foreach (var rule in rules) foreach (var rule in rules)
{ {
incrementResults.Add(await rule.AcquireAsync(context)); incrementResults.Add(await rule.AcquireAsync(context));
@ -81,7 +81,7 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
} }
} }
public virtual async Task<bool> IsAllowedAsync(string policyName, OperationRateLimitContext? context = null) public virtual async Task<bool> IsAllowedAsync(string policyName, OperationRateLimitingContext? context = null)
{ {
if (!Options.IsEnabled) if (!Options.IsEnabled)
{ {
@ -104,11 +104,11 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
return true; return true;
} }
public virtual async Task<OperationRateLimitResult> GetStatusAsync(string policyName, OperationRateLimitContext? context = null) public virtual async Task<OperationRateLimitingResult> GetStatusAsync(string policyName, OperationRateLimitingContext? context = null)
{ {
if (!Options.IsEnabled) if (!Options.IsEnabled)
{ {
return new OperationRateLimitResult return new OperationRateLimitingResult
{ {
IsAllowed = true, IsAllowed = true,
RemainingCount = int.MaxValue, RemainingCount = int.MaxValue,
@ -120,7 +120,7 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
context = EnsureContext(context); context = EnsureContext(context);
var policy = await PolicyProvider.GetAsync(policyName); var policy = await PolicyProvider.GetAsync(policyName);
var rules = CreateRules(policy); var rules = CreateRules(policy);
var ruleResults = new List<OperationRateLimitRuleResult>(); var ruleResults = new List<OperationRateLimitingRuleResult>();
foreach (var rule in rules) foreach (var rule in rules)
{ {
@ -130,7 +130,7 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
return AggregateResults(ruleResults, policy); return AggregateResults(ruleResults, policy);
} }
public virtual async Task ResetAsync(string policyName, OperationRateLimitContext? context = null) public virtual async Task ResetAsync(string policyName, OperationRateLimitingContext? context = null)
{ {
context = EnsureContext(context); context = EnsureContext(context);
var policy = await PolicyProvider.GetAsync(policyName); var policy = await PolicyProvider.GetAsync(policyName);
@ -142,20 +142,20 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
} }
} }
protected virtual OperationRateLimitContext EnsureContext(OperationRateLimitContext? context) protected virtual OperationRateLimitingContext EnsureContext(OperationRateLimitingContext? context)
{ {
context ??= new OperationRateLimitContext(); context ??= new OperationRateLimitingContext();
context.ServiceProvider = ServiceProvider; context.ServiceProvider = ServiceProvider;
return context; return context;
} }
protected virtual List<IOperationRateLimitRule> CreateRules(OperationRateLimitPolicy policy) protected virtual List<IOperationRateLimitingRule> CreateRules(OperationRateLimitingPolicy policy)
{ {
var rules = new List<IOperationRateLimitRule>(); var rules = new List<IOperationRateLimitingRule>();
for (var i = 0; i < policy.Rules.Count; i++) for (var i = 0; i < policy.Rules.Count; i++)
{ {
rules.Add(new FixedWindowOperationRateLimitRule( rules.Add(new FixedWindowOperationRateLimitingRule(
policy.Name, policy.Name,
i, i,
policy.Rules[i], policy.Rules[i],
@ -167,15 +167,15 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
foreach (var customRuleType in policy.CustomRuleTypes) foreach (var customRuleType in policy.CustomRuleTypes)
{ {
rules.Add((IOperationRateLimitRule)ServiceProvider.GetRequiredService(customRuleType)); rules.Add((IOperationRateLimitingRule)ServiceProvider.GetRequiredService(customRuleType));
} }
return rules; return rules;
} }
protected virtual OperationRateLimitResult AggregateResults( protected virtual OperationRateLimitingResult AggregateResults(
List<OperationRateLimitRuleResult> ruleResults, List<OperationRateLimitingRuleResult> ruleResults,
OperationRateLimitPolicy policy) OperationRateLimitingPolicy policy)
{ {
var isAllowed = ruleResults.All(r => r.IsAllowed); var isAllowed = ruleResults.All(r => r.IsAllowed);
var mostRestrictive = ruleResults var mostRestrictive = ruleResults
@ -183,7 +183,7 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
.ThenByDescending(r => r.RetryAfter ?? TimeSpan.Zero) .ThenByDescending(r => r.RetryAfter ?? TimeSpan.Zero)
.First(); .First();
return new OperationRateLimitResult return new OperationRateLimitingResult
{ {
IsAllowed = isAllowed, IsAllowed = isAllowed,
RemainingCount = mostRestrictive.RemainingCount, RemainingCount = mostRestrictive.RemainingCount,
@ -201,13 +201,13 @@ public class OperationRateLimitChecker : IOperationRateLimitChecker, ITransientD
} }
protected virtual void ThrowRateLimitException( protected virtual void ThrowRateLimitException(
OperationRateLimitPolicy policy, OperationRateLimitingPolicy policy,
OperationRateLimitResult result, OperationRateLimitingResult result,
OperationRateLimitContext context) OperationRateLimitingContext context)
{ {
var formatter = context.ServiceProvider.GetRequiredService<IOperationRateLimitFormatter>(); var formatter = context.ServiceProvider.GetRequiredService<IOperationRateLimitingFormatter>();
var exception = new AbpOperationRateLimitException( var exception = new AbpOperationRateLimitingException(
policy.Name, policy.Name,
result, result,
policy.ErrorCode); policy.ErrorCode);

6
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitContext.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingContext.cs

@ -2,9 +2,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitContext public class OperationRateLimitingContext
{ {
/// <summary> /// <summary>
/// Optional parameter passed by the caller. /// Optional parameter passed by the caller.
@ -14,7 +14,7 @@ public class OperationRateLimitContext
public string? Parameter { get; set; } public string? Parameter { get; set; }
/// <summary> /// <summary>
/// Additional properties that can be read by custom <see cref="IOperationRateLimitRule"/> implementations /// Additional properties that can be read by custom <see cref="IOperationRateLimitingRule"/> implementations
/// and are forwarded to the exception's Data dictionary when the rate limit is exceeded. /// and are forwarded to the exception's Data dictionary when the rate limit is exceeded.
/// </summary> /// </summary>
public Dictionary<string, object?> ExtraProperties { get; set; } = new(); public Dictionary<string, object?> ExtraProperties { get; set; } = new();

6
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitResult.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingResult.cs

@ -1,9 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitResult public class OperationRateLimitingResult
{ {
public bool IsAllowed { get; set; } public bool IsAllowed { get; set; }
@ -20,5 +20,5 @@ public class OperationRateLimitResult
/// <summary> /// <summary>
/// Detailed results per rule (for composite policies). /// Detailed results per rule (for composite policies).
/// </summary> /// </summary>
public List<OperationRateLimitRuleResult>? RuleResults { get; set; } public List<OperationRateLimitingRuleResult>? RuleResults { get; set; }
} }

4
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitRuleResult.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingRuleResult.cs

@ -1,8 +1,8 @@
using System; using System;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitRuleResult public class OperationRateLimitingRuleResult
{ {
public string RuleName { get; set; } = default!; public string RuleName { get; set; } = default!;

12
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/AbpOperationRateLimitException.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Exceptions/AbpOperationRateLimitingException.cs

@ -1,21 +1,21 @@
using System; using System;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class AbpOperationRateLimitException : BusinessException, IHasHttpStatusCode public class AbpOperationRateLimitingException : BusinessException, IHasHttpStatusCode
{ {
public string PolicyName { get; } public string PolicyName { get; }
public OperationRateLimitResult Result { get; } public OperationRateLimitingResult Result { get; }
public int HttpStatusCode => 429; public int HttpStatusCode => 429;
public AbpOperationRateLimitException( public AbpOperationRateLimitingException(
string policyName, string policyName,
OperationRateLimitResult result, OperationRateLimitingResult result,
string? errorCode = null) string? errorCode = null)
: base(code: errorCode ?? AbpOperationRateLimitErrorCodes.ExceedLimit) : base(code: errorCode ?? AbpOperationRateLimitingErrorCodes.ExceedLimit)
{ {
PolicyName = policyName; PolicyName = policyName;
Result = result; Result = result;

12
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/DefaultOperationRateLimitFormatter.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Formatting/DefaultOperationRateLimitingFormatter.cs

@ -2,15 +2,15 @@ using System;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class DefaultOperationRateLimitFormatter public class DefaultOperationRateLimitingFormatter
: IOperationRateLimitFormatter, ITransientDependency : IOperationRateLimitingFormatter, ITransientDependency
{ {
protected IStringLocalizer<AbpOperationRateLimitResource> Localizer { get; } protected IStringLocalizer<AbpOperationRateLimitingResource> Localizer { get; }
public DefaultOperationRateLimitFormatter( public DefaultOperationRateLimitingFormatter(
IStringLocalizer<AbpOperationRateLimitResource> localizer) IStringLocalizer<AbpOperationRateLimitingResource> localizer)
{ {
Localizer = localizer; Localizer = localizer;
} }

8
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Formatting/IOperationRateLimitingFormatter.cs

@ -0,0 +1,8 @@
using System;
namespace Volo.Abp.OperationRateLimiting;
public interface IOperationRateLimitingFormatter
{
string Format(TimeSpan duration);
}

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/ar.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/ar.json

@ -1,7 +1,7 @@
{ {
"culture": "ar", "culture": "ar",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "تم تجاوز حد معدل العملية. يمكنك المحاولة مرة أخرى بعد {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "تم تجاوز حد معدل العملية. يمكنك المحاولة مرة أخرى بعد {RetryAfter}.",
"RetryAfter:Years": "{0} سنة/سنوات", "RetryAfter:Years": "{0} سنة/سنوات",
"RetryAfter:YearsAndMonths": "{0} سنة/سنوات و {1} شهر/أشهر", "RetryAfter:YearsAndMonths": "{0} سنة/سنوات و {1} شهر/أشهر",
"RetryAfter:Months": "{0} شهر/أشهر", "RetryAfter:Months": "{0} شهر/أشهر",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/cs.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/cs.json

@ -1,7 +1,7 @@
{ {
"culture": "cs", "culture": "cs",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Překročen limit rychlosti operace. Můžete to zkusit znovu za {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Překročen limit rychlosti operace. Můžete to zkusit znovu za {RetryAfter}.",
"RetryAfter:Years": "{0} rok(y/let)", "RetryAfter:Years": "{0} rok(y/let)",
"RetryAfter:YearsAndMonths": "{0} rok(y/let) a {1} měsíc(e/ů)", "RetryAfter:YearsAndMonths": "{0} rok(y/let) a {1} měsíc(e/ů)",
"RetryAfter:Months": "{0} měsíc(e/ů)", "RetryAfter:Months": "{0} měsíc(e/ů)",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/de.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/de.json

@ -1,7 +1,7 @@
{ {
"culture": "de", "culture": "de",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Betriebsratenlimit überschritten. Sie können es nach {RetryAfter} erneut versuchen.", "Volo.Abp.OperationRateLimiting:010001": "Betriebsratenlimit überschritten. Sie können es nach {RetryAfter} erneut versuchen.",
"RetryAfter:Years": "{0} Jahr(e)", "RetryAfter:Years": "{0} Jahr(e)",
"RetryAfter:YearsAndMonths": "{0} Jahr(e) und {1} Monat(e)", "RetryAfter:YearsAndMonths": "{0} Jahr(e) und {1} Monat(e)",
"RetryAfter:Months": "{0} Monat(e)", "RetryAfter:Months": "{0} Monat(e)",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/el.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/el.json

@ -1,7 +1,7 @@
{ {
"culture": "el", "culture": "el",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Υπέρβαση ορίου ρυθμού λειτουργίας. Μπορείτε να δοκιμάσετε ξανά μετά από {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Υπέρβαση ορίου ρυθμού λειτουργίας. Μπορείτε να δοκιμάσετε ξανά μετά από {RetryAfter}.",
"RetryAfter:Years": "{0} έτος/η", "RetryAfter:Years": "{0} έτος/η",
"RetryAfter:YearsAndMonths": "{0} έτος/η και {1} μήνας/ες", "RetryAfter:YearsAndMonths": "{0} έτος/η και {1} μήνας/ες",
"RetryAfter:Months": "{0} μήνας/ες", "RetryAfter:Months": "{0} μήνας/ες",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/en-GB.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/en-GB.json

@ -1,7 +1,7 @@
{ {
"culture": "en-GB", "culture": "en-GB",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Operation rate limit exceeded. You can try again after {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Operation rate limit exceeded. You can try again after {RetryAfter}.",
"RetryAfter:Years": "{0} year(s)", "RetryAfter:Years": "{0} year(s)",
"RetryAfter:YearsAndMonths": "{0} year(s) and {1} month(s)", "RetryAfter:YearsAndMonths": "{0} year(s) and {1} month(s)",
"RetryAfter:Months": "{0} month(s)", "RetryAfter:Months": "{0} month(s)",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/en.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/en.json

@ -1,7 +1,7 @@
{ {
"culture": "en", "culture": "en",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Operation rate limit exceeded. You can try again after {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Operation rate limit exceeded. You can try again after {RetryAfter}.",
"RetryAfter:Years": "{0} year(s)", "RetryAfter:Years": "{0} year(s)",
"RetryAfter:YearsAndMonths": "{0} year(s) and {1} month(s)", "RetryAfter:YearsAndMonths": "{0} year(s) and {1} month(s)",
"RetryAfter:Months": "{0} month(s)", "RetryAfter:Months": "{0} month(s)",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/es.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/es.json

@ -1,7 +1,7 @@
{ {
"culture": "es", "culture": "es",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Se ha excedido el límite de tasa de operación. Puede intentarlo de nuevo después de {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Se ha excedido el límite de tasa de operación. Puede intentarlo de nuevo después de {RetryAfter}.",
"RetryAfter:Years": "{0} año(s)", "RetryAfter:Years": "{0} año(s)",
"RetryAfter:YearsAndMonths": "{0} año(s) y {1} mes(es)", "RetryAfter:YearsAndMonths": "{0} año(s) y {1} mes(es)",
"RetryAfter:Months": "{0} mes(es)", "RetryAfter:Months": "{0} mes(es)",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/fa.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/fa.json

@ -1,7 +1,7 @@
{ {
"culture": "fa", "culture": "fa",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "محدودیت نرخ عملیات فراتر رفته است. می‌توانید بعد از {RetryAfter} دوباره تلاش کنید.", "Volo.Abp.OperationRateLimiting:010001": "محدودیت نرخ عملیات فراتر رفته است. می‌توانید بعد از {RetryAfter} دوباره تلاش کنید.",
"RetryAfter:Years": "{0} سال", "RetryAfter:Years": "{0} سال",
"RetryAfter:YearsAndMonths": "{0} سال و {1} ماه", "RetryAfter:YearsAndMonths": "{0} سال و {1} ماه",
"RetryAfter:Months": "{0} ماه", "RetryAfter:Months": "{0} ماه",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/fi.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/fi.json

@ -1,7 +1,7 @@
{ {
"culture": "fi", "culture": "fi",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Toiminnon nopeusraja ylitetty. Voit yrittää uudelleen {RetryAfter} kuluttua.", "Volo.Abp.OperationRateLimiting:010001": "Toiminnon nopeusraja ylitetty. Voit yrittää uudelleen {RetryAfter} kuluttua.",
"RetryAfter:Years": "{0} vuosi/vuotta", "RetryAfter:Years": "{0} vuosi/vuotta",
"RetryAfter:YearsAndMonths": "{0} vuosi/vuotta ja {1} kuukausi/kuukautta", "RetryAfter:YearsAndMonths": "{0} vuosi/vuotta ja {1} kuukausi/kuukautta",
"RetryAfter:Months": "{0} kuukausi/kuukautta", "RetryAfter:Months": "{0} kuukausi/kuukautta",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/fr.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/fr.json

@ -1,7 +1,7 @@
{ {
"culture": "fr", "culture": "fr",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Limite de taux d'opération dépassée. Vous pouvez réessayer après {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Limite de taux d'opération dépassée. Vous pouvez réessayer après {RetryAfter}.",
"RetryAfter:Years": "{0} an(s)", "RetryAfter:Years": "{0} an(s)",
"RetryAfter:YearsAndMonths": "{0} an(s) et {1} mois", "RetryAfter:YearsAndMonths": "{0} an(s) et {1} mois",
"RetryAfter:Months": "{0} mois", "RetryAfter:Months": "{0} mois",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/hi.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/hi.json

@ -1,7 +1,7 @@
{ {
"culture": "hi", "culture": "hi",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "ऑपरेशन दर सीमा पार हो गई। आप {RetryAfter} के बाद पुनः प्रयास कर सकते हैं।", "Volo.Abp.OperationRateLimiting:010001": "ऑपरेशन दर सीमा पार हो गई। आप {RetryAfter} के बाद पुनः प्रयास कर सकते हैं।",
"RetryAfter:Years": "{0} वर्ष", "RetryAfter:Years": "{0} वर्ष",
"RetryAfter:YearsAndMonths": "{0} वर्ष और {1} महीना/महीने", "RetryAfter:YearsAndMonths": "{0} वर्ष और {1} महीना/महीने",
"RetryAfter:Months": "{0} महीना/महीने", "RetryAfter:Months": "{0} महीना/महीने",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/hr.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/hr.json

@ -1,7 +1,7 @@
{ {
"culture": "hr", "culture": "hr",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Prekoračeno ograničenje brzine operacije. Možete pokušati ponovo nakon {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Prekoračeno ograničenje brzine operacije. Možete pokušati ponovo nakon {RetryAfter}.",
"RetryAfter:Years": "{0} godina/e", "RetryAfter:Years": "{0} godina/e",
"RetryAfter:YearsAndMonths": "{0} godina/e i {1} mjesec/i", "RetryAfter:YearsAndMonths": "{0} godina/e i {1} mjesec/i",
"RetryAfter:Months": "{0} mjesec/i", "RetryAfter:Months": "{0} mjesec/i",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/hu.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/hu.json

@ -1,7 +1,7 @@
{ {
"culture": "hu", "culture": "hu",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "A műveleti sebességkorlát túllépve. Újra próbálkozhat {RetryAfter} múlva.", "Volo.Abp.OperationRateLimiting:010001": "A műveleti sebességkorlát túllépve. Újra próbálkozhat {RetryAfter} múlva.",
"RetryAfter:Years": "{0} év", "RetryAfter:Years": "{0} év",
"RetryAfter:YearsAndMonths": "{0} év és {1} hónap", "RetryAfter:YearsAndMonths": "{0} év és {1} hónap",
"RetryAfter:Months": "{0} hónap", "RetryAfter:Months": "{0} hónap",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/is.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/is.json

@ -1,7 +1,7 @@
{ {
"culture": "is", "culture": "is",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Aðgerðarhraðatakmörk náð. Þú getur reynt aftur eftir {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Aðgerðarhraðatakmörk náð. Þú getur reynt aftur eftir {RetryAfter}.",
"RetryAfter:Years": "{0} ár", "RetryAfter:Years": "{0} ár",
"RetryAfter:YearsAndMonths": "{0} ár og {1} mánuð(ir)", "RetryAfter:YearsAndMonths": "{0} ár og {1} mánuð(ir)",
"RetryAfter:Months": "{0} mánuð(ur/ir)", "RetryAfter:Months": "{0} mánuð(ur/ir)",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/it.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/it.json

@ -1,7 +1,7 @@
{ {
"culture": "it", "culture": "it",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Limite di frequenza operazione superato. Puoi riprovare dopo {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Limite di frequenza operazione superato. Puoi riprovare dopo {RetryAfter}.",
"RetryAfter:Years": "{0} anno/i", "RetryAfter:Years": "{0} anno/i",
"RetryAfter:YearsAndMonths": "{0} anno/i e {1} mese/i", "RetryAfter:YearsAndMonths": "{0} anno/i e {1} mese/i",
"RetryAfter:Months": "{0} mese/i", "RetryAfter:Months": "{0} mese/i",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/nl.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/nl.json

@ -1,7 +1,7 @@
{ {
"culture": "nl", "culture": "nl",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Bewerkingssnelheidslimiet overschreden. U kunt het opnieuw proberen na {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Bewerkingssnelheidslimiet overschreden. U kunt het opnieuw proberen na {RetryAfter}.",
"RetryAfter:Years": "{0} jaar", "RetryAfter:Years": "{0} jaar",
"RetryAfter:YearsAndMonths": "{0} jaar en {1} maand(en)", "RetryAfter:YearsAndMonths": "{0} jaar en {1} maand(en)",
"RetryAfter:Months": "{0} maand(en)", "RetryAfter:Months": "{0} maand(en)",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/pl-PL.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/pl-PL.json

@ -1,7 +1,7 @@
{ {
"culture": "pl-PL", "culture": "pl-PL",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Przekroczono limit częstotliwości operacji. Możesz spróbować ponownie po {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Przekroczono limit częstotliwości operacji. Możesz spróbować ponownie po {RetryAfter}.",
"RetryAfter:Years": "{0} rok/lat", "RetryAfter:Years": "{0} rok/lat",
"RetryAfter:YearsAndMonths": "{0} rok/lat i {1} miesiąc/miesięcy", "RetryAfter:YearsAndMonths": "{0} rok/lat i {1} miesiąc/miesięcy",
"RetryAfter:Months": "{0} miesiąc/miesięcy", "RetryAfter:Months": "{0} miesiąc/miesięcy",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/pt-BR.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/pt-BR.json

@ -1,7 +1,7 @@
{ {
"culture": "pt-BR", "culture": "pt-BR",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Limite de taxa de operação excedido. Você pode tentar novamente após {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Limite de taxa de operação excedido. Você pode tentar novamente após {RetryAfter}.",
"RetryAfter:Years": "{0} ano(s)", "RetryAfter:Years": "{0} ano(s)",
"RetryAfter:YearsAndMonths": "{0} ano(s) e {1} mês/meses", "RetryAfter:YearsAndMonths": "{0} ano(s) e {1} mês/meses",
"RetryAfter:Months": "{0} mês/meses", "RetryAfter:Months": "{0} mês/meses",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/ro-RO.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/ro-RO.json

@ -1,7 +1,7 @@
{ {
"culture": "ro-RO", "culture": "ro-RO",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Limita ratei de operare a fost depășită. Puteți încerca din nou după {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Limita ratei de operare a fost depășită. Puteți încerca din nou după {RetryAfter}.",
"RetryAfter:Years": "{0} an/ani", "RetryAfter:Years": "{0} an/ani",
"RetryAfter:YearsAndMonths": "{0} an/ani și {1} lună/luni", "RetryAfter:YearsAndMonths": "{0} an/ani și {1} lună/luni",
"RetryAfter:Months": "{0} lună/luni", "RetryAfter:Months": "{0} lună/luni",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/ru.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/ru.json

@ -1,7 +1,7 @@
{ {
"culture": "ru", "culture": "ru",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Превышен лимит частоты операций. Вы можете повторить попытку через {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Превышен лимит частоты операций. Вы можете повторить попытку через {RetryAfter}.",
"RetryAfter:Years": "{0} год/лет", "RetryAfter:Years": "{0} год/лет",
"RetryAfter:YearsAndMonths": "{0} год/лет и {1} месяц/месяцев", "RetryAfter:YearsAndMonths": "{0} год/лет и {1} месяц/месяцев",
"RetryAfter:Months": "{0} месяц/месяцев", "RetryAfter:Months": "{0} месяц/месяцев",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/sk.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/sk.json

@ -1,7 +1,7 @@
{ {
"culture": "sk", "culture": "sk",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Prekročený limit rýchlosti operácie. Môžete to skúsiť znova po {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Prekročený limit rýchlosti operácie. Môžete to skúsiť znova po {RetryAfter}.",
"RetryAfter:Years": "{0} rok/rokov", "RetryAfter:Years": "{0} rok/rokov",
"RetryAfter:YearsAndMonths": "{0} rok/rokov a {1} mesiac/mesiacov", "RetryAfter:YearsAndMonths": "{0} rok/rokov a {1} mesiac/mesiacov",
"RetryAfter:Months": "{0} mesiac/mesiacov", "RetryAfter:Months": "{0} mesiac/mesiacov",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/sl.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/sl.json

@ -1,7 +1,7 @@
{ {
"culture": "sl", "culture": "sl",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Presežena omejitev hitrosti operacije. Poskusite lahko znova čez {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Presežena omejitev hitrosti operacije. Poskusite lahko znova čez {RetryAfter}.",
"RetryAfter:Years": "{0} leto/let", "RetryAfter:Years": "{0} leto/let",
"RetryAfter:YearsAndMonths": "{0} leto/let in {1} mesec/mesecev", "RetryAfter:YearsAndMonths": "{0} leto/let in {1} mesec/mesecev",
"RetryAfter:Months": "{0} mesec/mesecev", "RetryAfter:Months": "{0} mesec/mesecev",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/sv.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/sv.json

@ -1,7 +1,7 @@
{ {
"culture": "sv", "culture": "sv",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Hastighetsgränsen för operationen har överskridits. Du kan försöka igen efter {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Hastighetsgränsen för operationen har överskridits. Du kan försöka igen efter {RetryAfter}.",
"RetryAfter:Years": "{0} år", "RetryAfter:Years": "{0} år",
"RetryAfter:YearsAndMonths": "{0} år och {1} månad(er)", "RetryAfter:YearsAndMonths": "{0} år och {1} månad(er)",
"RetryAfter:Months": "{0} månad(er)", "RetryAfter:Months": "{0} månad(er)",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/tr.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/tr.json

@ -1,7 +1,7 @@
{ {
"culture": "tr", "culture": "tr",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "İşlem hız sınırı aşıldı. {RetryAfter} sonra tekrar deneyebilirsiniz.", "Volo.Abp.OperationRateLimiting:010001": "İşlem hız sınırı aşıldı. {RetryAfter} sonra tekrar deneyebilirsiniz.",
"RetryAfter:Years": "{0} yıl", "RetryAfter:Years": "{0} yıl",
"RetryAfter:YearsAndMonths": "{0} yıl ve {1} ay", "RetryAfter:YearsAndMonths": "{0} yıl ve {1} ay",
"RetryAfter:Months": "{0} ay", "RetryAfter:Months": "{0} ay",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/vi.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/vi.json

@ -1,7 +1,7 @@
{ {
"culture": "vi", "culture": "vi",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "Đã vượt quá giới hạn tốc độ thao tác. Bạn có thể thử lại sau {RetryAfter}.", "Volo.Abp.OperationRateLimiting:010001": "Đã vượt quá giới hạn tốc độ thao tác. Bạn có thể thử lại sau {RetryAfter}.",
"RetryAfter:Years": "{0} năm", "RetryAfter:Years": "{0} năm",
"RetryAfter:YearsAndMonths": "{0} năm và {1} tháng", "RetryAfter:YearsAndMonths": "{0} năm và {1} tháng",
"RetryAfter:Months": "{0} tháng", "RetryAfter:Months": "{0} tháng",

2
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/Localization/zh-Hans.json → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Localization/zh-Hans.json

@ -1,7 +1,7 @@
{ {
"culture": "zh-Hans", "culture": "zh-Hans",
"texts": { "texts": {
"Volo.Abp.OperationRateLimit:010001": "操作频率超出限制。请在 {RetryAfter} 后重试。", "Volo.Abp.OperationRateLimiting:010001": "操作频率超出限制。请在 {RetryAfter} 后重试。",
"RetryAfter:Years": "{0} 年", "RetryAfter:Years": "{0} 年",
"RetryAfter:YearsAndMonths": "{0} 年 {1} 个月", "RetryAfter:YearsAndMonths": "{0} 年 {1} 个月",
"RetryAfter:Months": "{0} 个月", "RetryAfter:Months": "{0} 个月",

14
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/DefaultOperationRateLimitPolicyProvider.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/DefaultOperationRateLimitingPolicyProvider.cs

@ -4,30 +4,30 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class DefaultOperationRateLimitPolicyProvider : IOperationRateLimitPolicyProvider, ITransientDependency public class DefaultOperationRateLimitingPolicyProvider : IOperationRateLimitingPolicyProvider, ITransientDependency
{ {
protected AbpOperationRateLimitOptions Options { get; } protected AbpOperationRateLimitingOptions Options { get; }
public DefaultOperationRateLimitPolicyProvider(IOptions<AbpOperationRateLimitOptions> options) public DefaultOperationRateLimitingPolicyProvider(IOptions<AbpOperationRateLimitingOptions> options)
{ {
Options = options.Value; Options = options.Value;
} }
public virtual Task<OperationRateLimitPolicy> GetAsync(string policyName) public virtual Task<OperationRateLimitingPolicy> GetAsync(string policyName)
{ {
if (!Options.Policies.TryGetValue(policyName, out var policy)) if (!Options.Policies.TryGetValue(policyName, out var policy))
{ {
throw new AbpException( throw new AbpException(
$"Operation rate limit policy '{policyName}' was not found. " + $"Operation rate limit policy '{policyName}' was not found. " +
$"Make sure to configure it using AbpOperationRateLimitOptions.AddPolicy()."); $"Make sure to configure it using AbpOperationRateLimitingOptions.AddPolicy().");
} }
return Task.FromResult(policy); return Task.FromResult(policy);
} }
public virtual Task<List<OperationRateLimitPolicy>> GetListAsync() public virtual Task<List<OperationRateLimitingPolicy>> GetListAsync()
{ {
return Task.FromResult(Options.Policies.Values.ToList()); return Task.FromResult(Options.Policies.Values.ToList());
} }

11
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/IOperationRateLimitingPolicyProvider.cs

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimiting;
public interface IOperationRateLimitingPolicyProvider
{
Task<OperationRateLimitingPolicy> GetAsync(string policyName);
Task<List<OperationRateLimitingPolicy>> GetListAsync();
}

4
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitPartitionType.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingPartitionType.cs

@ -1,6 +1,6 @@
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public enum OperationRateLimitPartitionType public enum OperationRateLimitingPartitionType
{ {
Parameter, Parameter,
CurrentUser, CurrentUser,

6
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitPolicy.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingPolicy.cs

@ -1,15 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitPolicy public class OperationRateLimitingPolicy
{ {
public string Name { get; set; } = default!; public string Name { get; set; } = default!;
public string? ErrorCode { get; set; } public string? ErrorCode { get; set; }
public List<OperationRateLimitRuleDefinition> Rules { get; set; } = new(); public List<OperationRateLimitingRuleDefinition> Rules { get; set; } = new();
public List<Type> CustomRuleTypes { get; set; } = new(); public List<Type> CustomRuleTypes { get; set; } = new();
} }

34
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitPolicyBuilder.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingPolicyBuilder.cs

@ -2,16 +2,16 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitPolicyBuilder public class OperationRateLimitingPolicyBuilder
{ {
private readonly string _name; private readonly string _name;
private string? _errorCode; private string? _errorCode;
private readonly List<OperationRateLimitRuleDefinition> _rules = new(); private readonly List<OperationRateLimitingRuleDefinition> _rules = new();
private readonly List<Type> _customRuleTypes = new(); private readonly List<Type> _customRuleTypes = new();
public OperationRateLimitPolicyBuilder(string name) public OperationRateLimitingPolicyBuilder(string name)
{ {
_name = Check.NotNullOrWhiteSpace(name, nameof(name)); _name = Check.NotNullOrWhiteSpace(name, nameof(name));
} }
@ -19,10 +19,10 @@ public class OperationRateLimitPolicyBuilder
/// <summary> /// <summary>
/// Add a built-in rule. Multiple rules are AND-combined. /// Add a built-in rule. Multiple rules are AND-combined.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder AddRule( public OperationRateLimitingPolicyBuilder AddRule(
Action<OperationRateLimitRuleBuilder> configure) Action<OperationRateLimitingRuleBuilder> configure)
{ {
var builder = new OperationRateLimitRuleBuilder(this); var builder = new OperationRateLimitingRuleBuilder(this);
configure(builder); configure(builder);
if (!builder.IsCommitted) if (!builder.IsCommitted)
{ {
@ -34,8 +34,8 @@ public class OperationRateLimitPolicyBuilder
/// <summary> /// <summary>
/// Add a custom rule type (resolved from DI). /// Add a custom rule type (resolved from DI).
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder AddRule<TRule>() public OperationRateLimitingPolicyBuilder AddRule<TRule>()
where TRule : class, IOperationRateLimitRule where TRule : class, IOperationRateLimitingRule
{ {
_customRuleTypes.Add(typeof(TRule)); _customRuleTypes.Add(typeof(TRule));
return this; return this;
@ -45,10 +45,10 @@ public class OperationRateLimitPolicyBuilder
/// Shortcut: single-rule policy with fixed window. /// Shortcut: single-rule policy with fixed window.
/// Returns the rule builder for partition configuration. /// Returns the rule builder for partition configuration.
/// </summary> /// </summary>
public OperationRateLimitRuleBuilder WithFixedWindow( public OperationRateLimitingRuleBuilder WithFixedWindow(
TimeSpan duration, int maxCount) TimeSpan duration, int maxCount)
{ {
var builder = new OperationRateLimitRuleBuilder(this); var builder = new OperationRateLimitingRuleBuilder(this);
builder.WithFixedWindow(duration, maxCount); builder.WithFixedWindow(duration, maxCount);
return builder; return builder;
} }
@ -56,18 +56,18 @@ public class OperationRateLimitPolicyBuilder
/// <summary> /// <summary>
/// Set a custom ErrorCode for this policy's exception. /// Set a custom ErrorCode for this policy's exception.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder WithErrorCode(string errorCode) public OperationRateLimitingPolicyBuilder WithErrorCode(string errorCode)
{ {
_errorCode = Check.NotNullOrWhiteSpace(errorCode, nameof(errorCode)); _errorCode = Check.NotNullOrWhiteSpace(errorCode, nameof(errorCode));
return this; return this;
} }
internal void AddRuleDefinition(OperationRateLimitRuleDefinition definition) internal void AddRuleDefinition(OperationRateLimitingRuleDefinition definition)
{ {
_rules.Add(definition); _rules.Add(definition);
} }
internal OperationRateLimitPolicy Build() internal OperationRateLimitingPolicy Build()
{ {
if (_rules.Count == 0 && _customRuleTypes.Count == 0) if (_rules.Count == 0 && _customRuleTypes.Count == 0)
{ {
@ -77,7 +77,7 @@ public class OperationRateLimitPolicyBuilder
} }
var duplicate = _rules var duplicate = _rules
.Where(r => r.PartitionType != OperationRateLimitPartitionType.Custom) .Where(r => r.PartitionType != OperationRateLimitingPartitionType.Custom)
.GroupBy(r => (r.Duration, r.MaxCount, r.PartitionType, r.IsMultiTenant)) .GroupBy(r => (r.Duration, r.MaxCount, r.PartitionType, r.IsMultiTenant))
.FirstOrDefault(g => g.Count() > 1); .FirstOrDefault(g => g.Count() > 1);
@ -91,11 +91,11 @@ public class OperationRateLimitPolicyBuilder
"Each rule in a policy must have a unique combination of these properties."); "Each rule in a policy must have a unique combination of these properties.");
} }
return new OperationRateLimitPolicy return new OperationRateLimitingPolicy
{ {
Name = _name, Name = _name,
ErrorCode = _errorCode, ErrorCode = _errorCode,
Rules = new List<OperationRateLimitRuleDefinition>(_rules), Rules = new List<OperationRateLimitingRuleDefinition>(_rules),
CustomRuleTypes = new List<Type>(_customRuleTypes) CustomRuleTypes = new List<Type>(_customRuleTypes)
}; };
} }

52
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitRuleBuilder.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleBuilder.cs

@ -1,24 +1,24 @@
using System; using System;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitRuleBuilder public class OperationRateLimitingRuleBuilder
{ {
private readonly OperationRateLimitPolicyBuilder _policyBuilder; private readonly OperationRateLimitingPolicyBuilder _policyBuilder;
private TimeSpan _duration; private TimeSpan _duration;
private int _maxCount; private int _maxCount;
private OperationRateLimitPartitionType? _partitionType; private OperationRateLimitingPartitionType? _partitionType;
private Func<OperationRateLimitContext, string>? _customPartitionKeyResolver; private Func<OperationRateLimitingContext, string>? _customPartitionKeyResolver;
private bool _isMultiTenant; private bool _isMultiTenant;
internal bool IsCommitted { get; private set; } internal bool IsCommitted { get; private set; }
internal OperationRateLimitRuleBuilder(OperationRateLimitPolicyBuilder policyBuilder) internal OperationRateLimitingRuleBuilder(OperationRateLimitingPolicyBuilder policyBuilder)
{ {
_policyBuilder = policyBuilder; _policyBuilder = policyBuilder;
} }
public OperationRateLimitRuleBuilder WithFixedWindow( public OperationRateLimitingRuleBuilder WithFixedWindow(
TimeSpan duration, int maxCount) TimeSpan duration, int maxCount)
{ {
_duration = duration; _duration = duration;
@ -26,7 +26,7 @@ public class OperationRateLimitRuleBuilder
return this; return this;
} }
public OperationRateLimitRuleBuilder WithMultiTenancy() public OperationRateLimitingRuleBuilder WithMultiTenancy()
{ {
_isMultiTenant = true; _isMultiTenant = true;
return this; return this;
@ -35,9 +35,9 @@ public class OperationRateLimitRuleBuilder
/// <summary> /// <summary>
/// Use context.Parameter as partition key. /// Use context.Parameter as partition key.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder PartitionByParameter() public OperationRateLimitingPolicyBuilder PartitionByParameter()
{ {
_partitionType = OperationRateLimitPartitionType.Parameter; _partitionType = OperationRateLimitingPartitionType.Parameter;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder; return _policyBuilder;
} }
@ -45,9 +45,9 @@ public class OperationRateLimitRuleBuilder
/// <summary> /// <summary>
/// Auto resolve from ICurrentUser.Id. /// Auto resolve from ICurrentUser.Id.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder PartitionByCurrentUser() public OperationRateLimitingPolicyBuilder PartitionByCurrentUser()
{ {
_partitionType = OperationRateLimitPartitionType.CurrentUser; _partitionType = OperationRateLimitingPartitionType.CurrentUser;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder; return _policyBuilder;
} }
@ -55,9 +55,9 @@ public class OperationRateLimitRuleBuilder
/// <summary> /// <summary>
/// Auto resolve from ICurrentTenant.Id. /// Auto resolve from ICurrentTenant.Id.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder PartitionByCurrentTenant() public OperationRateLimitingPolicyBuilder PartitionByCurrentTenant()
{ {
_partitionType = OperationRateLimitPartitionType.CurrentTenant; _partitionType = OperationRateLimitingPartitionType.CurrentTenant;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder; return _policyBuilder;
} }
@ -65,9 +65,9 @@ public class OperationRateLimitRuleBuilder
/// <summary> /// <summary>
/// Auto resolve from IWebClientInfoProvider.ClientIpAddress. /// Auto resolve from IWebClientInfoProvider.ClientIpAddress.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder PartitionByClientIp() public OperationRateLimitingPolicyBuilder PartitionByClientIp()
{ {
_partitionType = OperationRateLimitPartitionType.ClientIp; _partitionType = OperationRateLimitingPartitionType.ClientIp;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder; return _policyBuilder;
} }
@ -76,9 +76,9 @@ public class OperationRateLimitRuleBuilder
/// Partition by email address. /// Partition by email address.
/// Resolves from context.Parameter, falls back to ICurrentUser.Email. /// Resolves from context.Parameter, falls back to ICurrentUser.Email.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder PartitionByEmail() public OperationRateLimitingPolicyBuilder PartitionByEmail()
{ {
_partitionType = OperationRateLimitPartitionType.Email; _partitionType = OperationRateLimitingPartitionType.Email;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder; return _policyBuilder;
} }
@ -87,9 +87,9 @@ public class OperationRateLimitRuleBuilder
/// Partition by phone number. /// Partition by phone number.
/// Resolves from context.Parameter, falls back to ICurrentUser.PhoneNumber. /// Resolves from context.Parameter, falls back to ICurrentUser.PhoneNumber.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder PartitionByPhoneNumber() public OperationRateLimitingPolicyBuilder PartitionByPhoneNumber()
{ {
_partitionType = OperationRateLimitPartitionType.PhoneNumber; _partitionType = OperationRateLimitingPartitionType.PhoneNumber;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder; return _policyBuilder;
} }
@ -97,10 +97,10 @@ public class OperationRateLimitRuleBuilder
/// <summary> /// <summary>
/// Custom partition key resolver from context. /// Custom partition key resolver from context.
/// </summary> /// </summary>
public OperationRateLimitPolicyBuilder PartitionBy( public OperationRateLimitingPolicyBuilder PartitionBy(
Func<OperationRateLimitContext, string> keyResolver) Func<OperationRateLimitingContext, string> keyResolver)
{ {
_partitionType = OperationRateLimitPartitionType.Custom; _partitionType = OperationRateLimitingPartitionType.Custom;
_customPartitionKeyResolver = Check.NotNull(keyResolver, nameof(keyResolver)); _customPartitionKeyResolver = Check.NotNull(keyResolver, nameof(keyResolver));
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder; return _policyBuilder;
@ -112,7 +112,7 @@ public class OperationRateLimitRuleBuilder
IsCommitted = true; IsCommitted = true;
} }
internal OperationRateLimitRuleDefinition Build() internal OperationRateLimitingRuleDefinition Build()
{ {
if (_duration <= TimeSpan.Zero) if (_duration <= TimeSpan.Zero)
{ {
@ -135,14 +135,14 @@ public class OperationRateLimitRuleBuilder
"Call PartitionByParameter(), PartitionByCurrentUser(), PartitionByClientIp(), or another PartitionBy*() method."); "Call PartitionByParameter(), PartitionByCurrentUser(), PartitionByClientIp(), or another PartitionBy*() method.");
} }
if (_partitionType == OperationRateLimitPartitionType.Custom && _customPartitionKeyResolver == null) if (_partitionType == OperationRateLimitingPartitionType.Custom && _customPartitionKeyResolver == null)
{ {
throw new AbpException( throw new AbpException(
"Custom partition type requires a key resolver. " + "Custom partition type requires a key resolver. " +
"Call PartitionBy(keyResolver) instead of setting partition type directly."); "Call PartitionBy(keyResolver) instead of setting partition type directly.");
} }
return new OperationRateLimitRuleDefinition return new OperationRateLimitingRuleDefinition
{ {
Duration = _duration, Duration = _duration,
MaxCount = _maxCount, MaxCount = _maxCount,

16
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleDefinition.cs

@ -0,0 +1,16 @@
using System;
namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitingRuleDefinition
{
public TimeSpan Duration { get; set; }
public int MaxCount { get; set; }
public OperationRateLimitingPartitionType PartitionType { get; set; }
public Func<OperationRateLimitingContext, string>? CustomPartitionKeyResolver { get; set; }
public bool IsMultiTenant { get; set; }
}

46
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/FixedWindowOperationRateLimitRule.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs

@ -3,25 +3,25 @@ using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Users; using Volo.Abp.Users;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class FixedWindowOperationRateLimitRule : IOperationRateLimitRule public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule
{ {
private const string HostTenantKey = "host"; private const string HostTenantKey = "host";
protected string PolicyName { get; } protected string PolicyName { get; }
protected int RuleIndex { get; } protected int RuleIndex { get; }
protected OperationRateLimitRuleDefinition Definition { get; } protected OperationRateLimitingRuleDefinition Definition { get; }
protected IOperationRateLimitStore Store { get; } protected IOperationRateLimitingStore Store { get; }
protected ICurrentUser CurrentUser { get; } protected ICurrentUser CurrentUser { get; }
protected ICurrentTenant CurrentTenant { get; } protected ICurrentTenant CurrentTenant { get; }
protected IWebClientInfoProvider WebClientInfoProvider { get; } protected IWebClientInfoProvider WebClientInfoProvider { get; }
public FixedWindowOperationRateLimitRule( public FixedWindowOperationRateLimitingRule(
string policyName, string policyName,
int ruleIndex, int ruleIndex,
OperationRateLimitRuleDefinition definition, OperationRateLimitingRuleDefinition definition,
IOperationRateLimitStore store, IOperationRateLimitingStore store,
ICurrentUser currentUser, ICurrentUser currentUser,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IWebClientInfoProvider webClientInfoProvider) IWebClientInfoProvider webClientInfoProvider)
@ -35,8 +35,8 @@ public class FixedWindowOperationRateLimitRule : IOperationRateLimitRule
WebClientInfoProvider = webClientInfoProvider; WebClientInfoProvider = webClientInfoProvider;
} }
public virtual async Task<OperationRateLimitRuleResult> AcquireAsync( public virtual async Task<OperationRateLimitingRuleResult> AcquireAsync(
OperationRateLimitContext context) OperationRateLimitingContext context)
{ {
var partitionKey = ResolvePartitionKey(context); var partitionKey = ResolvePartitionKey(context);
var storeKey = BuildStoreKey(partitionKey); var storeKey = BuildStoreKey(partitionKey);
@ -45,8 +45,8 @@ public class FixedWindowOperationRateLimitRule : IOperationRateLimitRule
return ToRuleResult(storeResult); return ToRuleResult(storeResult);
} }
public virtual async Task<OperationRateLimitRuleResult> CheckAsync( public virtual async Task<OperationRateLimitingRuleResult> CheckAsync(
OperationRateLimitContext context) OperationRateLimitingContext context)
{ {
var partitionKey = ResolvePartitionKey(context); var partitionKey = ResolvePartitionKey(context);
var storeKey = BuildStoreKey(partitionKey); var storeKey = BuildStoreKey(partitionKey);
@ -55,47 +55,47 @@ public class FixedWindowOperationRateLimitRule : IOperationRateLimitRule
return ToRuleResult(storeResult); return ToRuleResult(storeResult);
} }
public virtual async Task ResetAsync(OperationRateLimitContext context) public virtual async Task ResetAsync(OperationRateLimitingContext context)
{ {
var partitionKey = ResolvePartitionKey(context); var partitionKey = ResolvePartitionKey(context);
var storeKey = BuildStoreKey(partitionKey); var storeKey = BuildStoreKey(partitionKey);
await Store.ResetAsync(storeKey); await Store.ResetAsync(storeKey);
} }
protected virtual string ResolvePartitionKey(OperationRateLimitContext context) protected virtual string ResolvePartitionKey(OperationRateLimitingContext context)
{ {
return Definition.PartitionType switch return Definition.PartitionType switch
{ {
OperationRateLimitPartitionType.Parameter => OperationRateLimitingPartitionType.Parameter =>
context.Parameter ?? throw new AbpException( context.Parameter ?? throw new AbpException(
$"OperationRateLimitContext.Parameter is required for policy '{PolicyName}' (PartitionByParameter)."), $"OperationRateLimitingContext.Parameter is required for policy '{PolicyName}' (PartitionByParameter)."),
OperationRateLimitPartitionType.CurrentUser => OperationRateLimitingPartitionType.CurrentUser =>
CurrentUser.Id?.ToString() ?? throw new AbpException( CurrentUser.Id?.ToString() ?? throw new AbpException(
$"Current user is not authenticated. Policy '{PolicyName}' requires PartitionByCurrentUser."), $"Current user is not authenticated. Policy '{PolicyName}' requires PartitionByCurrentUser."),
OperationRateLimitPartitionType.CurrentTenant => OperationRateLimitingPartitionType.CurrentTenant =>
CurrentTenant.Id?.ToString() ?? HostTenantKey, CurrentTenant.Id?.ToString() ?? HostTenantKey,
OperationRateLimitPartitionType.ClientIp => OperationRateLimitingPartitionType.ClientIp =>
WebClientInfoProvider.ClientIpAddress WebClientInfoProvider.ClientIpAddress
?? throw new AbpException( ?? throw new AbpException(
$"Client IP address could not be determined. Policy '{PolicyName}' requires PartitionByClientIp. " + $"Client IP address could not be determined. Policy '{PolicyName}' requires PartitionByClientIp. " +
"Ensure IWebClientInfoProvider is properly configured."), "Ensure IWebClientInfoProvider is properly configured."),
OperationRateLimitPartitionType.Email => OperationRateLimitingPartitionType.Email =>
context.Parameter context.Parameter
?? CurrentUser.Email ?? CurrentUser.Email
?? throw new AbpException( ?? throw new AbpException(
$"Email is required for policy '{PolicyName}' (PartitionByEmail). Provide it via context.Parameter or ensure the user has an email."), $"Email is required for policy '{PolicyName}' (PartitionByEmail). Provide it via context.Parameter or ensure the user has an email."),
OperationRateLimitPartitionType.PhoneNumber => OperationRateLimitingPartitionType.PhoneNumber =>
context.Parameter context.Parameter
?? CurrentUser.PhoneNumber ?? CurrentUser.PhoneNumber
?? throw new AbpException( ?? throw new AbpException(
$"Phone number is required for policy '{PolicyName}' (PartitionByPhoneNumber). Provide it via context.Parameter or ensure the user has a phone number."), $"Phone number is required for policy '{PolicyName}' (PartitionByPhoneNumber). Provide it via context.Parameter or ensure the user has a phone number."),
OperationRateLimitPartitionType.Custom => OperationRateLimitingPartitionType.Custom =>
Definition.CustomPartitionKeyResolver!(context), Definition.CustomPartitionKeyResolver!(context),
_ => throw new AbpException($"Unknown partition type: {Definition.PartitionType}") _ => throw new AbpException($"Unknown partition type: {Definition.PartitionType}")
@ -119,9 +119,9 @@ public class FixedWindowOperationRateLimitRule : IOperationRateLimitRule
return $"orl:t:{tenantId}:{PolicyName}:{ruleKey}:{partitionKey}"; return $"orl:t:{tenantId}:{PolicyName}:{ruleKey}:{partitionKey}";
} }
protected virtual OperationRateLimitRuleResult ToRuleResult(OperationRateLimitStoreResult storeResult) protected virtual OperationRateLimitingRuleResult ToRuleResult(OperationRateLimitingStoreResult storeResult)
{ {
return new OperationRateLimitRuleResult return new OperationRateLimitingRuleResult
{ {
RuleName = $"{PolicyName}:Rule[{(long)Definition.Duration.TotalSeconds}s,{Definition.MaxCount},{Definition.PartitionType}]", RuleName = $"{PolicyName}:Rule[{(long)Definition.Duration.TotalSeconds}s,{Definition.MaxCount},{Definition.PartitionType}]",
IsAllowed = storeResult.IsAllowed, IsAllowed = storeResult.IsAllowed,

12
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/IOperationRateLimitingRule.cs

@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimiting;
public interface IOperationRateLimitingRule
{
Task<OperationRateLimitingRuleResult> AcquireAsync(OperationRateLimitingContext context);
Task<OperationRateLimitingRuleResult> CheckAsync(OperationRateLimitingContext context);
Task ResetAsync(OperationRateLimitingContext context);
}

38
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/DistributedCacheOperationRateLimitStore.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Store/DistributedCacheOperationRateLimitingStore.cs

@ -7,20 +7,20 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking; using Volo.Abp.DistributedLocking;
using Volo.Abp.Timing; using Volo.Abp.Timing;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore, ITransientDependency public class DistributedCacheOperationRateLimitingStore : IOperationRateLimitingStore, ITransientDependency
{ {
protected IDistributedCache<OperationRateLimitCacheItem> Cache { get; } protected IDistributedCache<OperationRateLimitingCacheItem> Cache { get; }
protected IClock Clock { get; } protected IClock Clock { get; }
protected IAbpDistributedLock DistributedLock { get; } protected IAbpDistributedLock DistributedLock { get; }
protected AbpOperationRateLimitOptions Options { get; } protected AbpOperationRateLimitingOptions Options { get; }
public DistributedCacheOperationRateLimitStore( public DistributedCacheOperationRateLimitingStore(
IDistributedCache<OperationRateLimitCacheItem> cache, IDistributedCache<OperationRateLimitingCacheItem> cache,
IClock clock, IClock clock,
IAbpDistributedLock distributedLock, IAbpDistributedLock distributedLock,
IOptions<AbpOperationRateLimitOptions> options) IOptions<AbpOperationRateLimitingOptions> options)
{ {
Cache = cache; Cache = cache;
Clock = clock; Clock = clock;
@ -28,12 +28,12 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
Options = options.Value; Options = options.Value;
} }
public virtual async Task<OperationRateLimitStoreResult> IncrementAsync( public virtual async Task<OperationRateLimitingStoreResult> IncrementAsync(
string key, TimeSpan duration, int maxCount) string key, TimeSpan duration, int maxCount)
{ {
if (maxCount <= 0) if (maxCount <= 0)
{ {
return new OperationRateLimitStoreResult return new OperationRateLimitingStoreResult
{ {
IsAllowed = false, IsAllowed = false,
CurrentCount = 0, CurrentCount = 0,
@ -43,7 +43,7 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
} }
await using (var handle = await DistributedLock.TryAcquireAsync( await using (var handle = await DistributedLock.TryAcquireAsync(
$"OperationRateLimit:{key}", Options.LockTimeout)) $"OperationRateLimiting:{key}", Options.LockTimeout))
{ {
if (handle == null) if (handle == null)
{ {
@ -57,14 +57,14 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
if (cacheItem == null || now >= cacheItem.WindowStart.Add(duration)) if (cacheItem == null || now >= cacheItem.WindowStart.Add(duration))
{ {
cacheItem = new OperationRateLimitCacheItem { Count = 1, WindowStart = now }; cacheItem = new OperationRateLimitingCacheItem { Count = 1, WindowStart = now };
await Cache.SetAsync(key, cacheItem, await Cache.SetAsync(key, cacheItem,
new DistributedCacheEntryOptions new DistributedCacheEntryOptions
{ {
AbsoluteExpirationRelativeToNow = duration AbsoluteExpirationRelativeToNow = duration
}); });
return new OperationRateLimitStoreResult return new OperationRateLimitingStoreResult
{ {
IsAllowed = true, IsAllowed = true,
CurrentCount = 1, CurrentCount = 1,
@ -75,7 +75,7 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
if (cacheItem.Count >= maxCount) if (cacheItem.Count >= maxCount)
{ {
var retryAfter = cacheItem.WindowStart.Add(duration) - now; var retryAfter = cacheItem.WindowStart.Add(duration) - now;
return new OperationRateLimitStoreResult return new OperationRateLimitingStoreResult
{ {
IsAllowed = false, IsAllowed = false,
CurrentCount = cacheItem.Count, CurrentCount = cacheItem.Count,
@ -92,7 +92,7 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
AbsoluteExpirationRelativeToNow = expiration > TimeSpan.Zero ? expiration : duration AbsoluteExpirationRelativeToNow = expiration > TimeSpan.Zero ? expiration : duration
}); });
return new OperationRateLimitStoreResult return new OperationRateLimitingStoreResult
{ {
IsAllowed = true, IsAllowed = true,
CurrentCount = cacheItem.Count, CurrentCount = cacheItem.Count,
@ -101,12 +101,12 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
} }
} }
public virtual async Task<OperationRateLimitStoreResult> GetAsync( public virtual async Task<OperationRateLimitingStoreResult> GetAsync(
string key, TimeSpan duration, int maxCount) string key, TimeSpan duration, int maxCount)
{ {
if (maxCount <= 0) if (maxCount <= 0)
{ {
return new OperationRateLimitStoreResult return new OperationRateLimitingStoreResult
{ {
IsAllowed = false, IsAllowed = false,
CurrentCount = 0, CurrentCount = 0,
@ -120,7 +120,7 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
if (cacheItem == null || now >= cacheItem.WindowStart.Add(duration)) if (cacheItem == null || now >= cacheItem.WindowStart.Add(duration))
{ {
return new OperationRateLimitStoreResult return new OperationRateLimitingStoreResult
{ {
IsAllowed = true, IsAllowed = true,
CurrentCount = 0, CurrentCount = 0,
@ -131,7 +131,7 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
if (cacheItem.Count >= maxCount) if (cacheItem.Count >= maxCount)
{ {
var retryAfter = cacheItem.WindowStart.Add(duration) - now; var retryAfter = cacheItem.WindowStart.Add(duration) - now;
return new OperationRateLimitStoreResult return new OperationRateLimitingStoreResult
{ {
IsAllowed = false, IsAllowed = false,
CurrentCount = cacheItem.Count, CurrentCount = cacheItem.Count,
@ -140,7 +140,7 @@ public class DistributedCacheOperationRateLimitStore : IOperationRateLimitStore,
}; };
} }
return new OperationRateLimitStoreResult return new OperationRateLimitingStoreResult
{ {
IsAllowed = true, IsAllowed = true,
CurrentCount = cacheItem.Count, CurrentCount = cacheItem.Count,

13
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Store/IOperationRateLimitingStore.cs

@ -0,0 +1,13 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimiting;
public interface IOperationRateLimitingStore
{
Task<OperationRateLimitingStoreResult> IncrementAsync(string key, TimeSpan duration, int maxCount);
Task<OperationRateLimitingStoreResult> GetAsync(string key, TimeSpan duration, int maxCount);
Task ResetAsync(string key);
}

6
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitCacheItem.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Store/OperationRateLimitingCacheItem.cs

@ -2,11 +2,11 @@ using System;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
[CacheName("OperationRateLimit")] [CacheName("OperationRateLimiting")]
[IgnoreMultiTenancy] [IgnoreMultiTenancy]
public class OperationRateLimitCacheItem public class OperationRateLimitingCacheItem
{ {
public int Count { get; set; } public int Count { get; set; }

4
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitStoreResult.cs → framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Store/OperationRateLimitingStoreResult.cs

@ -1,8 +1,8 @@
using System; using System;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitStoreResult public class OperationRateLimitingStoreResult
{ {
public bool IsAllowed { get; set; } public bool IsAllowed { get; set; }

2
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo.Abp.OperationRateLimit.Tests.csproj → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo.Abp.OperationRateLimiting.Tests.csproj

@ -11,7 +11,7 @@
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> <ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.ExceptionHandling\Volo.Abp.ExceptionHandling.csproj" /> <ProjectReference Include="..\..\src\Volo.Abp.ExceptionHandling\Volo.Abp.ExceptionHandling.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> <ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.OperationRateLimit\Volo.Abp.OperationRateLimit.csproj" /> <ProjectReference Include="..\..\src\Volo.Abp.OperationRateLimiting\Volo.Abp.OperationRateLimiting.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup> </ItemGroup>

26
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/AbpOperationRateLimitException_Tests.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingException_Tests.cs

@ -2,14 +2,14 @@ using System;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class AbpOperationRateLimitException_Tests public class AbpOperationRateLimitingException_Tests
{ {
[Fact] [Fact]
public void Should_Set_HttpStatusCode_To_429() public void Should_Set_HttpStatusCode_To_429()
{ {
var result = new OperationRateLimitResult var result = new OperationRateLimitingResult
{ {
IsAllowed = false, IsAllowed = false,
MaxCount = 3, MaxCount = 3,
@ -18,7 +18,7 @@ public class AbpOperationRateLimitException_Tests
RetryAfter = TimeSpan.FromMinutes(15) RetryAfter = TimeSpan.FromMinutes(15)
}; };
var exception = new AbpOperationRateLimitException("TestPolicy", result); var exception = new AbpOperationRateLimitingException("TestPolicy", result);
exception.HttpStatusCode.ShouldBe(429); exception.HttpStatusCode.ShouldBe(429);
} }
@ -26,7 +26,7 @@ public class AbpOperationRateLimitException_Tests
[Fact] [Fact]
public void Should_Set_Default_ErrorCode() public void Should_Set_Default_ErrorCode()
{ {
var result = new OperationRateLimitResult var result = new OperationRateLimitingResult
{ {
IsAllowed = false, IsAllowed = false,
MaxCount = 3, MaxCount = 3,
@ -34,15 +34,15 @@ public class AbpOperationRateLimitException_Tests
RemainingCount = 0 RemainingCount = 0
}; };
var exception = new AbpOperationRateLimitException("TestPolicy", result); var exception = new AbpOperationRateLimitingException("TestPolicy", result);
exception.Code.ShouldBe(AbpOperationRateLimitErrorCodes.ExceedLimit); exception.Code.ShouldBe(AbpOperationRateLimitingErrorCodes.ExceedLimit);
} }
[Fact] [Fact]
public void Should_Set_Custom_ErrorCode() public void Should_Set_Custom_ErrorCode()
{ {
var result = new OperationRateLimitResult var result = new OperationRateLimitingResult
{ {
IsAllowed = false, IsAllowed = false,
MaxCount = 3, MaxCount = 3,
@ -50,7 +50,7 @@ public class AbpOperationRateLimitException_Tests
RemainingCount = 0 RemainingCount = 0
}; };
var exception = new AbpOperationRateLimitException("TestPolicy", result, "App:Custom:Error"); var exception = new AbpOperationRateLimitingException("TestPolicy", result, "App:Custom:Error");
exception.Code.ShouldBe("App:Custom:Error"); exception.Code.ShouldBe("App:Custom:Error");
} }
@ -58,7 +58,7 @@ public class AbpOperationRateLimitException_Tests
[Fact] [Fact]
public void Should_Include_Data_Properties() public void Should_Include_Data_Properties()
{ {
var result = new OperationRateLimitResult var result = new OperationRateLimitingResult
{ {
IsAllowed = false, IsAllowed = false,
MaxCount = 3, MaxCount = 3,
@ -68,7 +68,7 @@ public class AbpOperationRateLimitException_Tests
WindowDuration = TimeSpan.FromHours(1) WindowDuration = TimeSpan.FromHours(1)
}; };
var exception = new AbpOperationRateLimitException("TestPolicy", result); var exception = new AbpOperationRateLimitingException("TestPolicy", result);
exception.Data["PolicyName"].ShouldBe("TestPolicy"); exception.Data["PolicyName"].ShouldBe("TestPolicy");
exception.Data["MaxCount"].ShouldBe(3); exception.Data["MaxCount"].ShouldBe(3);
@ -82,7 +82,7 @@ public class AbpOperationRateLimitException_Tests
[Fact] [Fact]
public void Should_Store_PolicyName_And_Result() public void Should_Store_PolicyName_And_Result()
{ {
var result = new OperationRateLimitResult var result = new OperationRateLimitingResult
{ {
IsAllowed = false, IsAllowed = false,
MaxCount = 5, MaxCount = 5,
@ -91,7 +91,7 @@ public class AbpOperationRateLimitException_Tests
RetryAfter = TimeSpan.FromHours(1) RetryAfter = TimeSpan.FromHours(1)
}; };
var exception = new AbpOperationRateLimitException("MyPolicy", result); var exception = new AbpOperationRateLimitingException("MyPolicy", result);
exception.PolicyName.ShouldBe("MyPolicy"); exception.PolicyName.ShouldBe("MyPolicy");
exception.Result.ShouldBeSameAs(result); exception.Result.ShouldBeSameAs(result);

20
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/AbpOperationRateLimitPhase2RaceTestModule.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingPhase2RaceTestModule.cs

@ -6,18 +6,18 @@ using Volo.Abp.Autofac;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
/// <summary> /// <summary>
/// A mock store that simulates a concurrent race condition: /// A mock store that simulates a concurrent race condition:
/// - GetAsync always says the quota is available (Phase 1 checks pass). /// - GetAsync always says the quota is available (Phase 1 checks pass).
/// - IncrementAsync always says the quota is exhausted (Phase 2 finds another request consumed it). /// - IncrementAsync always says the quota is exhausted (Phase 2 finds another request consumed it).
/// </summary> /// </summary>
internal class RaceConditionSimulatorStore : IOperationRateLimitStore internal class RaceConditionSimulatorStore : IOperationRateLimitingStore
{ {
public Task<OperationRateLimitStoreResult> GetAsync(string key, TimeSpan duration, int maxCount) public Task<OperationRateLimitingStoreResult> GetAsync(string key, TimeSpan duration, int maxCount)
{ {
return Task.FromResult(new OperationRateLimitStoreResult return Task.FromResult(new OperationRateLimitingStoreResult
{ {
IsAllowed = true, IsAllowed = true,
CurrentCount = 0, CurrentCount = 0,
@ -25,10 +25,10 @@ internal class RaceConditionSimulatorStore : IOperationRateLimitStore
}); });
} }
public Task<OperationRateLimitStoreResult> IncrementAsync(string key, TimeSpan duration, int maxCount) public Task<OperationRateLimitingStoreResult> IncrementAsync(string key, TimeSpan duration, int maxCount)
{ {
// Simulate: between Phase 1 and Phase 2 another concurrent request consumed the last slot. // Simulate: between Phase 1 and Phase 2 another concurrent request consumed the last slot.
return Task.FromResult(new OperationRateLimitStoreResult return Task.FromResult(new OperationRateLimitingStoreResult
{ {
IsAllowed = false, IsAllowed = false,
CurrentCount = maxCount, CurrentCount = maxCount,
@ -44,19 +44,19 @@ internal class RaceConditionSimulatorStore : IOperationRateLimitStore
} }
[DependsOn( [DependsOn(
typeof(AbpOperationRateLimitModule), typeof(AbpOperationRateLimitingModule),
typeof(AbpExceptionHandlingModule), typeof(AbpExceptionHandlingModule),
typeof(AbpTestBaseModule), typeof(AbpTestBaseModule),
typeof(AbpAutofacModule) typeof(AbpAutofacModule)
)] )]
public class AbpOperationRateLimitPhase2RaceTestModule : AbpModule public class AbpOperationRateLimitingPhase2RaceTestModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.Replace( context.Services.Replace(
ServiceDescriptor.Transient<IOperationRateLimitStore, RaceConditionSimulatorStore>()); ServiceDescriptor.Transient<IOperationRateLimitingStore, RaceConditionSimulatorStore>());
Configure<AbpOperationRateLimitOptions>(options => Configure<AbpOperationRateLimitingOptions>(options =>
{ {
options.AddPolicy("TestRacePolicy", policy => options.AddPolicy("TestRacePolicy", policy =>
{ {

8
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/AbpOperationRateLimitTestModule.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingTestModule.cs

@ -6,15 +6,15 @@ using Volo.Abp.Autofac;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
[DependsOn( [DependsOn(
typeof(AbpOperationRateLimitModule), typeof(AbpOperationRateLimitingModule),
typeof(AbpExceptionHandlingModule), typeof(AbpExceptionHandlingModule),
typeof(AbpTestBaseModule), typeof(AbpTestBaseModule),
typeof(AbpAutofacModule) typeof(AbpAutofacModule)
)] )]
public class AbpOperationRateLimitTestModule : AbpModule public class AbpOperationRateLimitingTestModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
@ -22,7 +22,7 @@ public class AbpOperationRateLimitTestModule : AbpModule
mockWebClientInfoProvider.ClientIpAddress.Returns("127.0.0.1"); mockWebClientInfoProvider.ClientIpAddress.Returns("127.0.0.1");
context.Services.AddSingleton<IWebClientInfoProvider>(mockWebClientInfoProvider); context.Services.AddSingleton<IWebClientInfoProvider>(mockWebClientInfoProvider);
Configure<AbpOperationRateLimitOptions>(options => Configure<AbpOperationRateLimitingOptions>(options =>
{ {
options.AddPolicy("TestSimple", policy => options.AddPolicy("TestSimple", policy =>
{ {

10
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/DistributedCacheOperationRateLimitStore_Tests.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/DistributedCacheOperationRateLimitingStore_Tests.cs

@ -3,15 +3,15 @@ using System.Threading.Tasks;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class DistributedCacheOperationRateLimitStore_Tests : OperationRateLimitTestBase public class DistributedCacheOperationRateLimitingStore_Tests : OperationRateLimitingTestBase
{ {
private readonly IOperationRateLimitStore _store; private readonly IOperationRateLimitingStore _store;
public DistributedCacheOperationRateLimitStore_Tests() public DistributedCacheOperationRateLimitingStore_Tests()
{ {
_store = GetRequiredService<IOperationRateLimitStore>(); _store = GetRequiredService<IOperationRateLimitingStore>();
} }
[Fact] [Fact]

36
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/OperationRateLimitCheckerFixes_Tests.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingCheckerFixes_Tests.cs

@ -4,19 +4,19 @@ using Shouldly;
using Volo.Abp.Testing; using Volo.Abp.Testing;
using Xunit; using Xunit;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
/// <summary> /// <summary>
/// Tests for Fix #6: Phase 1 in CheckAsync now checks ALL rules before throwing, /// Tests for Fix #6: Phase 1 in CheckAsync now checks ALL rules before throwing,
/// so RetryAfter is the maximum across all blocking rules and RuleResults is complete. /// so RetryAfter is the maximum across all blocking rules and RuleResults is complete.
/// </summary> /// </summary>
public class OperationRateLimitCheckerPhase1_Tests : OperationRateLimitTestBase public class OperationRateLimitingCheckerPhase1_Tests : OperationRateLimitingTestBase
{ {
private readonly IOperationRateLimitChecker _checker; private readonly IOperationRateLimitingChecker _checker;
public OperationRateLimitCheckerPhase1_Tests() public OperationRateLimitingCheckerPhase1_Tests()
{ {
_checker = GetRequiredService<IOperationRateLimitChecker>(); _checker = GetRequiredService<IOperationRateLimitingChecker>();
} }
[Fact] [Fact]
@ -25,14 +25,14 @@ public class OperationRateLimitCheckerPhase1_Tests : OperationRateLimitTestBase
// TestCompositeMaxRetryAfter: Rule0 (5-min window, max=1), Rule1 (2-hr window, max=1) // TestCompositeMaxRetryAfter: Rule0 (5-min window, max=1), Rule1 (2-hr window, max=1)
// Both rules use PartitionByParameter with the same key, so one request exhausts both. // Both rules use PartitionByParameter with the same key, so one request exhausts both.
var param = $"max-retry-{Guid.NewGuid()}"; var param = $"max-retry-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// First request: both rules go from 0 to 1 (exhausted, since maxCount=1) // First request: both rules go from 0 to 1 (exhausted, since maxCount=1)
await _checker.CheckAsync("TestCompositeMaxRetryAfter", context); await _checker.CheckAsync("TestCompositeMaxRetryAfter", context);
// Second request: both Rule0 and Rule1 are blocking. // Second request: both Rule0 and Rule1 are blocking.
// Phase 1 checks all rules → RetryAfter must be the larger one (~2 hours). // Phase 1 checks all rules → RetryAfter must be the larger one (~2 hours).
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestCompositeMaxRetryAfter", context); await _checker.CheckAsync("TestCompositeMaxRetryAfter", context);
}); });
@ -46,12 +46,12 @@ public class OperationRateLimitCheckerPhase1_Tests : OperationRateLimitTestBase
public async Task Should_Include_All_Rules_In_RuleResults_When_Multiple_Rules_Block() public async Task Should_Include_All_Rules_In_RuleResults_When_Multiple_Rules_Block()
{ {
var param = $"all-rules-{Guid.NewGuid()}"; var param = $"all-rules-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// Exhaust both rules // Exhaust both rules
await _checker.CheckAsync("TestCompositeMaxRetryAfter", context); await _checker.CheckAsync("TestCompositeMaxRetryAfter", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestCompositeMaxRetryAfter", context); await _checker.CheckAsync("TestCompositeMaxRetryAfter", context);
}); });
@ -69,12 +69,12 @@ public class OperationRateLimitCheckerPhase1_Tests : OperationRateLimitTestBase
// TestCompositePartialBlock: Rule0 (max=1) blocks, Rule1 (max=100) is still within limit. // TestCompositePartialBlock: Rule0 (max=1) blocks, Rule1 (max=100) is still within limit.
// RuleResults must contain BOTH rules so callers get the full picture. // RuleResults must contain BOTH rules so callers get the full picture.
var param = $"partial-block-{Guid.NewGuid()}"; var param = $"partial-block-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// Exhaust only Rule0 (max=1) // Exhaust only Rule0 (max=1)
await _checker.CheckAsync("TestCompositePartialBlock", context); await _checker.CheckAsync("TestCompositePartialBlock", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestCompositePartialBlock", context); await _checker.CheckAsync("TestCompositePartialBlock", context);
}); });
@ -102,8 +102,8 @@ public class OperationRateLimitCheckerPhase1_Tests : OperationRateLimitTestBase
/// Uses a mock store that simulates a concurrent race condition: /// Uses a mock store that simulates a concurrent race condition:
/// GetAsync (Phase 1) always reports quota available, but IncrementAsync (Phase 2) returns denied. /// GetAsync (Phase 1) always reports quota available, but IncrementAsync (Phase 2) returns denied.
/// </summary> /// </summary>
public class OperationRateLimitCheckerPhase2Race_Tests public class OperationRateLimitingCheckerPhase2Race_Tests
: AbpIntegratedTest<AbpOperationRateLimitPhase2RaceTestModule> : AbpIntegratedTest<AbpOperationRateLimitingPhase2RaceTestModule>
{ {
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{ {
@ -116,10 +116,10 @@ public class OperationRateLimitCheckerPhase2Race_Tests
// The mock store always returns IsAllowed=true in GetAsync (Phase 1 passes) // The mock store always returns IsAllowed=true in GetAsync (Phase 1 passes)
// but always returns IsAllowed=false in IncrementAsync (simulates concurrent exhaustion). // but always returns IsAllowed=false in IncrementAsync (simulates concurrent exhaustion).
// Before Fix #1, CheckAsync would silently succeed. After the fix it must throw. // Before Fix #1, CheckAsync would silently succeed. After the fix it must throw.
var checker = GetRequiredService<IOperationRateLimitChecker>(); var checker = GetRequiredService<IOperationRateLimitingChecker>();
var context = new OperationRateLimitContext { Parameter = "race-test" }; var context = new OperationRateLimitingContext { Parameter = "race-test" };
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await checker.CheckAsync("TestRacePolicy", context); await checker.CheckAsync("TestRacePolicy", context);
}); });
@ -134,8 +134,8 @@ public class OperationRateLimitCheckerPhase2Race_Tests
{ {
// IsAllowedAsync is read-only and does not call IncrementAsync, // IsAllowedAsync is read-only and does not call IncrementAsync,
// so it should not be affected by the mock store's deny-on-increment behavior. // so it should not be affected by the mock store's deny-on-increment behavior.
var checker = GetRequiredService<IOperationRateLimitChecker>(); var checker = GetRequiredService<IOperationRateLimitingChecker>();
var context = new OperationRateLimitContext { Parameter = "is-allowed-race" }; var context = new OperationRateLimitingContext { Parameter = "is-allowed-race" };
// Should return true because GetAsync always returns allowed in the mock store // Should return true because GetAsync always returns allowed in the mock store
var allowed = await checker.IsAllowedAsync("TestRacePolicy", context); var allowed = await checker.IsAllowedAsync("TestRacePolicy", context);

130
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/OperationRateLimitChecker_Tests.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingChecker_Tests.cs

@ -6,21 +6,21 @@ using Shouldly;
using Volo.Abp.Security.Claims; using Volo.Abp.Security.Claims;
using Xunit; using Xunit;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase public class OperationRateLimitingChecker_Tests : OperationRateLimitingTestBase
{ {
private readonly IOperationRateLimitChecker _checker; private readonly IOperationRateLimitingChecker _checker;
public OperationRateLimitChecker_Tests() public OperationRateLimitingChecker_Tests()
{ {
_checker = GetRequiredService<IOperationRateLimitChecker>(); _checker = GetRequiredService<IOperationRateLimitingChecker>();
} }
[Fact] [Fact]
public async Task Should_Allow_Within_Limit() public async Task Should_Allow_Within_Limit()
{ {
var context = new OperationRateLimitContext { Parameter = "test@example.com" }; var context = new OperationRateLimitingContext { Parameter = "test@example.com" };
// Should not throw for 3 requests (max is 3) // Should not throw for 3 requests (max is 3)
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
@ -32,13 +32,13 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Reject_When_Exceeded() public async Task Should_Reject_When_Exceeded()
{ {
var param = $"exceed-{Guid.NewGuid()}"; var param = $"exceed-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
}); });
@ -46,14 +46,14 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
exception.PolicyName.ShouldBe("TestSimple"); exception.PolicyName.ShouldBe("TestSimple");
exception.Result.IsAllowed.ShouldBeFalse(); exception.Result.IsAllowed.ShouldBeFalse();
exception.HttpStatusCode.ShouldBe(429); exception.HttpStatusCode.ShouldBe(429);
exception.Code.ShouldBe(AbpOperationRateLimitErrorCodes.ExceedLimit); exception.Code.ShouldBe(AbpOperationRateLimitingErrorCodes.ExceedLimit);
} }
[Fact] [Fact]
public async Task Should_Return_Correct_RemainingCount() public async Task Should_Return_Correct_RemainingCount()
{ {
var param = $"remaining-{Guid.NewGuid()}"; var param = $"remaining-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
var status = await _checker.GetStatusAsync("TestSimple", context); var status = await _checker.GetStatusAsync("TestSimple", context);
status.IsAllowed.ShouldBeTrue(); status.IsAllowed.ShouldBeTrue();
@ -73,13 +73,13 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Return_Correct_RetryAfter() public async Task Should_Return_Correct_RetryAfter()
{ {
var param = $"retry-{Guid.NewGuid()}"; var param = $"retry-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
}); });
@ -100,8 +100,8 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
using (principalAccessor.Change(claimsPrincipal)) using (principalAccessor.Change(claimsPrincipal))
{ {
var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitChecker>(); var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitingChecker>();
var context = new OperationRateLimitContext { Parameter = $"composite-{Guid.NewGuid()}" }; var context = new OperationRateLimitingContext { Parameter = $"composite-{Guid.NewGuid()}" };
// Should pass: both rules within limits // Should pass: both rules within limits
await checker.CheckAsync("TestComposite", context); await checker.CheckAsync("TestComposite", context);
@ -123,16 +123,16 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
using (principalAccessor.Change(claimsPrincipal)) using (principalAccessor.Change(claimsPrincipal))
{ {
var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitChecker>(); var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitingChecker>();
var param = $"composite-reject-{Guid.NewGuid()}"; var param = $"composite-reject-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
await checker.CheckAsync("TestComposite", context); await checker.CheckAsync("TestComposite", context);
await checker.CheckAsync("TestComposite", context); await checker.CheckAsync("TestComposite", context);
await checker.CheckAsync("TestComposite", context); await checker.CheckAsync("TestComposite", context);
// 4th request: Rule1 (max 3 per hour by parameter) should fail // 4th request: Rule1 (max 3 per hour by parameter) should fail
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await checker.CheckAsync("TestComposite", context); await checker.CheckAsync("TestComposite", context);
}); });
@ -146,14 +146,14 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Reset_Counter() public async Task Should_Reset_Counter()
{ {
var param = $"reset-{Guid.NewGuid()}"; var param = $"reset-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
// Should be at limit // Should be at limit
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
}); });
@ -169,12 +169,12 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Use_Custom_ErrorCode() public async Task Should_Use_Custom_ErrorCode()
{ {
var param = $"custom-error-{Guid.NewGuid()}"; var param = $"custom-error-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestCustomErrorCode", context); await _checker.CheckAsync("TestCustomErrorCode", context);
await _checker.CheckAsync("TestCustomErrorCode", context); await _checker.CheckAsync("TestCustomErrorCode", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestCustomErrorCode", context); await _checker.CheckAsync("TestCustomErrorCode", context);
}); });
@ -194,7 +194,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
[Fact] [Fact]
public async Task Should_Skip_When_Disabled() public async Task Should_Skip_When_Disabled()
{ {
var options = GetRequiredService<Microsoft.Extensions.Options.IOptions<AbpOperationRateLimitOptions>>(); var options = GetRequiredService<Microsoft.Extensions.Options.IOptions<AbpOperationRateLimitingOptions>>();
var originalValue = options.Value.IsEnabled; var originalValue = options.Value.IsEnabled;
try try
@ -202,7 +202,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
options.Value.IsEnabled = false; options.Value.IsEnabled = false;
var param = $"disabled-{Guid.NewGuid()}"; var param = $"disabled-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// Should pass unlimited times // Should pass unlimited times
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
@ -220,7 +220,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Work_With_IsAllowedAsync() public async Task Should_Work_With_IsAllowedAsync()
{ {
var param = $"is-allowed-{Guid.NewGuid()}"; var param = $"is-allowed-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// IsAllowedAsync does not consume quota // IsAllowedAsync does not consume quota
(await _checker.IsAllowedAsync("TestSimple", context)).ShouldBeTrue(); (await _checker.IsAllowedAsync("TestSimple", context)).ShouldBeTrue();
@ -245,8 +245,8 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
var param1 = $"param1-{Guid.NewGuid()}"; var param1 = $"param1-{Guid.NewGuid()}";
var param2 = $"param2-{Guid.NewGuid()}"; var param2 = $"param2-{Guid.NewGuid()}";
var context1 = new OperationRateLimitContext { Parameter = param1 }; var context1 = new OperationRateLimitingContext { Parameter = param1 };
var context2 = new OperationRateLimitContext { Parameter = param2 }; var context2 = new OperationRateLimitingContext { Parameter = param2 };
// Consume all for param1 // Consume all for param1
await _checker.CheckAsync("TestSimple", context1); await _checker.CheckAsync("TestSimple", context1);
@ -262,7 +262,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Support_ExtraProperties_In_Exception_Data() public async Task Should_Support_ExtraProperties_In_Exception_Data()
{ {
var param = $"extra-{Guid.NewGuid()}"; var param = $"extra-{Guid.NewGuid()}";
var context = new OperationRateLimitContext var context = new OperationRateLimitingContext
{ {
Parameter = param, Parameter = param,
ExtraProperties = ExtraProperties =
@ -276,7 +276,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
}); });
@ -291,13 +291,13 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Partition_By_Email_Via_Parameter() public async Task Should_Partition_By_Email_Via_Parameter()
{ {
var email = $"email-param-{Guid.NewGuid()}@example.com"; var email = $"email-param-{Guid.NewGuid()}@example.com";
var context = new OperationRateLimitContext { Parameter = email }; var context = new OperationRateLimitingContext { Parameter = email };
await _checker.CheckAsync("TestEmailBased", context); await _checker.CheckAsync("TestEmailBased", context);
await _checker.CheckAsync("TestEmailBased", context); await _checker.CheckAsync("TestEmailBased", context);
await _checker.CheckAsync("TestEmailBased", context); await _checker.CheckAsync("TestEmailBased", context);
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestEmailBased", context); await _checker.CheckAsync("TestEmailBased", context);
}); });
@ -315,16 +315,16 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
using (principalAccessor.Change(claimsPrincipal)) using (principalAccessor.Change(claimsPrincipal))
{ {
var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitChecker>(); var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitingChecker>();
// No Parameter set, should fall back to ICurrentUser.Email // No Parameter set, should fall back to ICurrentUser.Email
var context = new OperationRateLimitContext(); var context = new OperationRateLimitingContext();
await checker.CheckAsync("TestEmailBased", context); await checker.CheckAsync("TestEmailBased", context);
await checker.CheckAsync("TestEmailBased", context); await checker.CheckAsync("TestEmailBased", context);
await checker.CheckAsync("TestEmailBased", context); await checker.CheckAsync("TestEmailBased", context);
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await checker.CheckAsync("TestEmailBased", context); await checker.CheckAsync("TestEmailBased", context);
}); });
@ -336,13 +336,13 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Partition_By_PhoneNumber_Via_Parameter() public async Task Should_Partition_By_PhoneNumber_Via_Parameter()
{ {
var phone = $"phone-param-{Guid.NewGuid()}"; var phone = $"phone-param-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = phone }; var context = new OperationRateLimitingContext { Parameter = phone };
await _checker.CheckAsync("TestPhoneNumberBased", context); await _checker.CheckAsync("TestPhoneNumberBased", context);
await _checker.CheckAsync("TestPhoneNumberBased", context); await _checker.CheckAsync("TestPhoneNumberBased", context);
await _checker.CheckAsync("TestPhoneNumberBased", context); await _checker.CheckAsync("TestPhoneNumberBased", context);
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestPhoneNumberBased", context); await _checker.CheckAsync("TestPhoneNumberBased", context);
}); });
@ -360,16 +360,16 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
using (principalAccessor.Change(claimsPrincipal)) using (principalAccessor.Change(claimsPrincipal))
{ {
var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitChecker>(); var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitingChecker>();
// No Parameter set, should fall back to ICurrentUser.PhoneNumber // No Parameter set, should fall back to ICurrentUser.PhoneNumber
var context = new OperationRateLimitContext(); var context = new OperationRateLimitingContext();
await checker.CheckAsync("TestPhoneNumberBased", context); await checker.CheckAsync("TestPhoneNumberBased", context);
await checker.CheckAsync("TestPhoneNumberBased", context); await checker.CheckAsync("TestPhoneNumberBased", context);
await checker.CheckAsync("TestPhoneNumberBased", context); await checker.CheckAsync("TestPhoneNumberBased", context);
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await checker.CheckAsync("TestPhoneNumberBased", context); await checker.CheckAsync("TestPhoneNumberBased", context);
}); });
@ -381,7 +381,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Throw_When_Email_Not_Available() public async Task Should_Throw_When_Email_Not_Available()
{ {
// No Parameter and no authenticated user // No Parameter and no authenticated user
var context = new OperationRateLimitContext(); var context = new OperationRateLimitingContext();
await Assert.ThrowsAsync<AbpException>(async () => await Assert.ThrowsAsync<AbpException>(async () =>
{ {
@ -403,16 +403,16 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
using (principalAccessor.Change(claimsPrincipal)) using (principalAccessor.Change(claimsPrincipal))
{ {
var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitChecker>(); var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitingChecker>();
var param = $"no-waste-{Guid.NewGuid()}"; var param = $"no-waste-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// 2 successful requests (Rule1: 2/5, Rule2: 2/2) // 2 successful requests (Rule1: 2/5, Rule2: 2/2)
await checker.CheckAsync("TestCompositeRule2First", context); await checker.CheckAsync("TestCompositeRule2First", context);
await checker.CheckAsync("TestCompositeRule2First", context); await checker.CheckAsync("TestCompositeRule2First", context);
// 3rd request: Rule2 blocks (2/2 at max) // 3rd request: Rule2 blocks (2/2 at max)
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await checker.CheckAsync("TestCompositeRule2First", context); await checker.CheckAsync("TestCompositeRule2First", context);
}); });
@ -442,7 +442,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
// TestCompositeParamIp: Rule1 (Parameter, 5/hour), Rule2 (ClientIp, 3/hour) // TestCompositeParamIp: Rule1 (Parameter, 5/hour), Rule2 (ClientIp, 3/hour)
// IP limit (3) is lower, should trigger first // IP limit (3) is lower, should trigger first
var param = $"param-ip-{Guid.NewGuid()}"; var param = $"param-ip-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// 3 successful requests // 3 successful requests
await _checker.CheckAsync("TestCompositeParamIp", context); await _checker.CheckAsync("TestCompositeParamIp", context);
@ -450,7 +450,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
await _checker.CheckAsync("TestCompositeParamIp", context); await _checker.CheckAsync("TestCompositeParamIp", context);
// 4th: IP rule blocks (3/3) // 4th: IP rule blocks (3/3)
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestCompositeParamIp", context); await _checker.CheckAsync("TestCompositeParamIp", context);
}); });
@ -475,8 +475,8 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
// but share the same Rule2 (IP) counter // but share the same Rule2 (IP) counter
var param1 = $"share-ip-1-{Guid.NewGuid()}"; var param1 = $"share-ip-1-{Guid.NewGuid()}";
var param2 = $"share-ip-2-{Guid.NewGuid()}"; var param2 = $"share-ip-2-{Guid.NewGuid()}";
var context1 = new OperationRateLimitContext { Parameter = param1 }; var context1 = new OperationRateLimitingContext { Parameter = param1 };
var context2 = new OperationRateLimitContext { Parameter = param2 }; var context2 = new OperationRateLimitingContext { Parameter = param2 };
// 2 requests with param1 // 2 requests with param1
await _checker.CheckAsync("TestCompositeParamIp", context1); await _checker.CheckAsync("TestCompositeParamIp", context1);
@ -486,7 +486,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
await _checker.CheckAsync("TestCompositeParamIp", context2); await _checker.CheckAsync("TestCompositeParamIp", context2);
// 4th request with param2: IP rule blocks (3/3 from combined) // 4th request with param2: IP rule blocks (3/3 from combined)
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestCompositeParamIp", context2); await _checker.CheckAsync("TestCompositeParamIp", context2);
}); });
@ -516,9 +516,9 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
using (principalAccessor.Change(claimsPrincipal)) using (principalAccessor.Change(claimsPrincipal))
{ {
var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitChecker>(); var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitingChecker>();
var param = $"triple-{Guid.NewGuid()}"; var param = $"triple-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// 3 successful requests // 3 successful requests
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
@ -526,7 +526,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
// 4th: IP rule blocks (3/3) // 4th: IP rule blocks (3/3)
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
}); });
@ -559,9 +559,9 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
using (principalAccessor.Change(claimsPrincipal)) using (principalAccessor.Change(claimsPrincipal))
{ {
var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitChecker>(); var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitingChecker>();
var param = $"triple-nowaste-{Guid.NewGuid()}"; var param = $"triple-nowaste-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// 3 successful requests (all rules increment to 3) // 3 successful requests (all rules increment to 3)
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
@ -571,7 +571,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
// Attempt 3 more blocked requests // Attempt 3 more blocked requests
for (var i = 0; i < 3; i++) for (var i = 0; i < 3; i++)
{ {
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
}); });
@ -599,16 +599,16 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
using (principalAccessor.Change(claimsPrincipal)) using (principalAccessor.Change(claimsPrincipal))
{ {
var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitChecker>(); var checker = scope.ServiceProvider.GetRequiredService<IOperationRateLimitingChecker>();
var param = $"triple-reset-{Guid.NewGuid()}"; var param = $"triple-reset-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// Exhaust IP limit // Exhaust IP limit
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await checker.CheckAsync("TestCompositeTriple", context); await checker.CheckAsync("TestCompositeTriple", context);
}); });
@ -633,7 +633,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
public async Task Should_Throw_When_PhoneNumber_Not_Available() public async Task Should_Throw_When_PhoneNumber_Not_Available()
{ {
// No Parameter and no authenticated user // No Parameter and no authenticated user
var context = new OperationRateLimitContext(); var context = new OperationRateLimitingContext();
await Assert.ThrowsAsync<AbpException>(async () => await Assert.ThrowsAsync<AbpException>(async () =>
{ {
@ -644,9 +644,9 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
[Fact] [Fact]
public async Task Should_Deny_First_Request_When_MaxCount_Is_Zero() public async Task Should_Deny_First_Request_When_MaxCount_Is_Zero()
{ {
var context = new OperationRateLimitContext { Parameter = $"ban-{Guid.NewGuid()}" }; var context = new OperationRateLimitingContext { Parameter = $"ban-{Guid.NewGuid()}" };
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestBanPolicy", context); await _checker.CheckAsync("TestBanPolicy", context);
}); });
@ -659,7 +659,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
[Fact] [Fact]
public async Task Should_IsAllowed_Return_False_When_MaxCount_Is_Zero() public async Task Should_IsAllowed_Return_False_When_MaxCount_Is_Zero()
{ {
var context = new OperationRateLimitContext { Parameter = $"ban-allowed-{Guid.NewGuid()}" }; var context = new OperationRateLimitingContext { Parameter = $"ban-allowed-{Guid.NewGuid()}" };
var allowed = await _checker.IsAllowedAsync("TestBanPolicy", context); var allowed = await _checker.IsAllowedAsync("TestBanPolicy", context);
allowed.ShouldBeFalse(); allowed.ShouldBeFalse();
@ -668,7 +668,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
[Fact] [Fact]
public async Task Should_GetStatus_Show_Not_Allowed_When_MaxCount_Is_Zero() public async Task Should_GetStatus_Show_Not_Allowed_When_MaxCount_Is_Zero()
{ {
var context = new OperationRateLimitContext { Parameter = $"ban-status-{Guid.NewGuid()}" }; var context = new OperationRateLimitingContext { Parameter = $"ban-status-{Guid.NewGuid()}" };
var status = await _checker.GetStatusAsync("TestBanPolicy", context); var status = await _checker.GetStatusAsync("TestBanPolicy", context);
status.IsAllowed.ShouldBeFalse(); status.IsAllowed.ShouldBeFalse();
@ -684,14 +684,14 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
var param1 = $"op1-{Guid.NewGuid()}"; var param1 = $"op1-{Guid.NewGuid()}";
var param2 = $"op2-{Guid.NewGuid()}"; var param2 = $"op2-{Guid.NewGuid()}";
var ctx1 = new OperationRateLimitContext { Parameter = param1 }; var ctx1 = new OperationRateLimitingContext { Parameter = param1 };
var ctx2 = new OperationRateLimitContext { Parameter = param2 }; var ctx2 = new OperationRateLimitingContext { Parameter = param2 };
// Exhaust param1's quota (max=2) // Exhaust param1's quota (max=2)
await _checker.CheckAsync("TestCustomResolver", ctx1); await _checker.CheckAsync("TestCustomResolver", ctx1);
await _checker.CheckAsync("TestCustomResolver", ctx1); await _checker.CheckAsync("TestCustomResolver", ctx1);
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestCustomResolver", ctx1); await _checker.CheckAsync("TestCustomResolver", ctx1);
}); });
@ -704,7 +704,7 @@ public class OperationRateLimitChecker_Tests : OperationRateLimitTestBase
[Fact] [Fact]
public void Should_Throw_When_Policy_Has_Duplicate_Rules() public void Should_Throw_When_Policy_Has_Duplicate_Rules()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
Assert.Throws<AbpException>(() => Assert.Throws<AbpException>(() =>
{ {

38
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/OperationRateLimitFrontendIntegration_Tests.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingFrontendIntegration_Tests.cs

@ -6,19 +6,19 @@ using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Xunit; using Xunit;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTestBase public class OperationRateLimitingFrontendIntegration_Tests : OperationRateLimitingTestBase
{ {
private readonly IOperationRateLimitChecker _checker; private readonly IOperationRateLimitingChecker _checker;
private readonly IExceptionToErrorInfoConverter _errorInfoConverter; private readonly IExceptionToErrorInfoConverter _errorInfoConverter;
private readonly IOperationRateLimitFormatter _formatter; private readonly IOperationRateLimitingFormatter _formatter;
public OperationRateLimitFrontendIntegration_Tests() public OperationRateLimitingFrontendIntegration_Tests()
{ {
_checker = GetRequiredService<IOperationRateLimitChecker>(); _checker = GetRequiredService<IOperationRateLimitingChecker>();
_errorInfoConverter = GetRequiredService<IExceptionToErrorInfoConverter>(); _errorInfoConverter = GetRequiredService<IExceptionToErrorInfoConverter>();
_formatter = GetRequiredService<IOperationRateLimitFormatter>(); _formatter = GetRequiredService<IOperationRateLimitingFormatter>();
} }
[Fact] [Fact]
@ -27,13 +27,13 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
using (CultureHelper.Use("en")) using (CultureHelper.Use("en"))
{ {
var param = $"frontend-en-{Guid.NewGuid()}"; var param = $"frontend-en-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
}); });
@ -52,13 +52,13 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
using (CultureHelper.Use("zh-Hans")) using (CultureHelper.Use("zh-Hans"))
{ {
var param = $"frontend-zh-{Guid.NewGuid()}"; var param = $"frontend-zh-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
}); });
@ -75,7 +75,7 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
public async Task ErrorInfo_Should_Include_Structured_Data_For_Frontend() public async Task ErrorInfo_Should_Include_Structured_Data_For_Frontend()
{ {
var param = $"frontend-data-{Guid.NewGuid()}"; var param = $"frontend-data-{Guid.NewGuid()}";
var context = new OperationRateLimitContext var context = new OperationRateLimitingContext
{ {
Parameter = param, Parameter = param,
ExtraProperties = ExtraProperties =
@ -88,7 +88,7 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
}); });
@ -96,7 +96,7 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
var errorInfo = _errorInfoConverter.Convert(exception); var errorInfo = _errorInfoConverter.Convert(exception);
// Frontend receives error.code // Frontend receives error.code
errorInfo.Code.ShouldBe(AbpOperationRateLimitErrorCodes.ExceedLimit); errorInfo.Code.ShouldBe(AbpOperationRateLimitingErrorCodes.ExceedLimit);
// Frontend receives error.data for countdown timer and UI display // Frontend receives error.data for countdown timer and UI display
exception.Data["PolicyName"].ShouldBe("TestSimple"); exception.Data["PolicyName"].ShouldBe("TestSimple");
@ -144,7 +144,7 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
public async Task GetStatusAsync_Should_Provide_Countdown_Data_For_Frontend() public async Task GetStatusAsync_Should_Provide_Countdown_Data_For_Frontend()
{ {
var param = $"frontend-status-{Guid.NewGuid()}"; var param = $"frontend-status-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// Before any requests: frontend can show "3 remaining" // Before any requests: frontend can show "3 remaining"
var status = await _checker.GetStatusAsync("TestSimple", context); var status = await _checker.GetStatusAsync("TestSimple", context);
@ -180,12 +180,12 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
public async Task Custom_ErrorCode_Should_Appear_In_ErrorInfo() public async Task Custom_ErrorCode_Should_Appear_In_ErrorInfo()
{ {
var param = $"frontend-custom-code-{Guid.NewGuid()}"; var param = $"frontend-custom-code-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestCustomErrorCode", context); await _checker.CheckAsync("TestCustomErrorCode", context);
await _checker.CheckAsync("TestCustomErrorCode", context); await _checker.CheckAsync("TestCustomErrorCode", context);
var exception = await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => var exception = await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestCustomErrorCode", context); await _checker.CheckAsync("TestCustomErrorCode", context);
}); });
@ -364,7 +364,7 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
public async Task Reset_Should_Allow_Frontend_To_Resume() public async Task Reset_Should_Allow_Frontend_To_Resume()
{ {
var param = $"frontend-reset-{Guid.NewGuid()}"; var param = $"frontend-reset-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// Exhaust limit // Exhaust limit
await _checker.CheckAsync("TestSimple", context); await _checker.CheckAsync("TestSimple", context);
@ -389,7 +389,7 @@ public class OperationRateLimitFrontendIntegration_Tests : OperationRateLimitTes
public async Task IsAllowedAsync_Can_Be_Used_For_Frontend_PreCheck() public async Task IsAllowedAsync_Can_Be_Used_For_Frontend_PreCheck()
{ {
var param = $"frontend-precheck-{Guid.NewGuid()}"; var param = $"frontend-precheck-{Guid.NewGuid()}";
var context = new OperationRateLimitContext { Parameter = param }; var context = new OperationRateLimitingContext { Parameter = param };
// Frontend precheck: button should be enabled // Frontend precheck: button should be enabled
(await _checker.IsAllowedAsync("TestSimple", context)).ShouldBeTrue(); (await _checker.IsAllowedAsync("TestSimple", context)).ShouldBeTrue();

28
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/OperationRateLimitMultiTenant_Tests.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingMultiTenant_Tests.cs

@ -5,24 +5,24 @@ using Shouldly;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Xunit; using Xunit;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
/// <summary> /// <summary>
/// Verifies per-tenant isolation for tenant-scoped partition types and /// Verifies per-tenant isolation for tenant-scoped partition types and
/// global (cross-tenant) sharing for ClientIp partition type. /// global (cross-tenant) sharing for ClientIp partition type.
/// </summary> /// </summary>
public class OperationRateLimitMultiTenant_Tests : OperationRateLimitTestBase public class OperationRateLimitingMultiTenant_Tests : OperationRateLimitingTestBase
{ {
private readonly ICurrentTenant _currentTenant; private readonly ICurrentTenant _currentTenant;
private readonly IOperationRateLimitChecker _checker; private readonly IOperationRateLimitingChecker _checker;
private static readonly Guid TenantA = Guid.NewGuid(); private static readonly Guid TenantA = Guid.NewGuid();
private static readonly Guid TenantB = Guid.NewGuid(); private static readonly Guid TenantB = Guid.NewGuid();
public OperationRateLimitMultiTenant_Tests() public OperationRateLimitingMultiTenant_Tests()
{ {
_currentTenant = GetRequiredService<ICurrentTenant>(); _currentTenant = GetRequiredService<ICurrentTenant>();
_checker = GetRequiredService<IOperationRateLimitChecker>(); _checker = GetRequiredService<IOperationRateLimitingChecker>();
} }
[Fact] [Fact]
@ -33,12 +33,12 @@ public class OperationRateLimitMultiTenant_Tests : OperationRateLimitTestBase
using (_currentTenant.Change(TenantA)) using (_currentTenant.Change(TenantA))
{ {
var ctx = new OperationRateLimitContext { Parameter = param }; var ctx = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestMultiTenantByParameter", ctx); await _checker.CheckAsync("TestMultiTenantByParameter", ctx);
await _checker.CheckAsync("TestMultiTenantByParameter", ctx); await _checker.CheckAsync("TestMultiTenantByParameter", ctx);
// Tenant A exhausted (max=2) // Tenant A exhausted (max=2)
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestMultiTenantByParameter", ctx); await _checker.CheckAsync("TestMultiTenantByParameter", ctx);
}); });
@ -46,7 +46,7 @@ public class OperationRateLimitMultiTenant_Tests : OperationRateLimitTestBase
using (_currentTenant.Change(TenantB)) using (_currentTenant.Change(TenantB))
{ {
var ctx = new OperationRateLimitContext { Parameter = param }; var ctx = new OperationRateLimitingContext { Parameter = param };
// Tenant B has its own counter and should still be allowed // Tenant B has its own counter and should still be allowed
await _checker.CheckAsync("TestMultiTenantByParameter", ctx); await _checker.CheckAsync("TestMultiTenantByParameter", ctx);
@ -63,17 +63,17 @@ public class OperationRateLimitMultiTenant_Tests : OperationRateLimitTestBase
using (_currentTenant.Change(TenantA)) using (_currentTenant.Change(TenantA))
{ {
var ctx = new OperationRateLimitContext(); var ctx = new OperationRateLimitingContext();
await _checker.CheckAsync("TestMultiTenantByClientIp", ctx); await _checker.CheckAsync("TestMultiTenantByClientIp", ctx);
await _checker.CheckAsync("TestMultiTenantByClientIp", ctx); await _checker.CheckAsync("TestMultiTenantByClientIp", ctx);
} }
using (_currentTenant.Change(TenantB)) using (_currentTenant.Change(TenantB))
{ {
var ctx = new OperationRateLimitContext(); var ctx = new OperationRateLimitingContext();
// Tenant B shares the same IP counter; should be at limit now // Tenant B shares the same IP counter; should be at limit now
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestMultiTenantByClientIp", ctx); await _checker.CheckAsync("TestMultiTenantByClientIp", ctx);
}); });
@ -87,10 +87,10 @@ public class OperationRateLimitMultiTenant_Tests : OperationRateLimitTestBase
var param = $"host-vs-tenant-{Guid.NewGuid()}"; var param = $"host-vs-tenant-{Guid.NewGuid()}";
// Host context: exhaust quota // Host context: exhaust quota
var hostCtx = new OperationRateLimitContext { Parameter = param }; var hostCtx = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestMultiTenantByParameter", hostCtx); await _checker.CheckAsync("TestMultiTenantByParameter", hostCtx);
await _checker.CheckAsync("TestMultiTenantByParameter", hostCtx); await _checker.CheckAsync("TestMultiTenantByParameter", hostCtx);
await Assert.ThrowsAsync<AbpOperationRateLimitException>(async () => await Assert.ThrowsAsync<AbpOperationRateLimitingException>(async () =>
{ {
await _checker.CheckAsync("TestMultiTenantByParameter", hostCtx); await _checker.CheckAsync("TestMultiTenantByParameter", hostCtx);
}); });
@ -98,7 +98,7 @@ public class OperationRateLimitMultiTenant_Tests : OperationRateLimitTestBase
// Tenant A should have its own counter, unaffected by host // Tenant A should have its own counter, unaffected by host
using (_currentTenant.Change(TenantA)) using (_currentTenant.Change(TenantA))
{ {
var tenantCtx = new OperationRateLimitContext { Parameter = param }; var tenantCtx = new OperationRateLimitingContext { Parameter = param };
await _checker.CheckAsync("TestMultiTenantByParameter", tenantCtx); await _checker.CheckAsync("TestMultiTenantByParameter", tenantCtx);
(await _checker.IsAllowedAsync("TestMultiTenantByParameter", tenantCtx)).ShouldBeTrue(); (await _checker.IsAllowedAsync("TestMultiTenantByParameter", tenantCtx)).ShouldBeTrue();
} }

54
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/OperationRateLimitPolicyBuilder_Tests.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingPolicyBuilder_Tests.cs

@ -2,14 +2,14 @@ using System;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitPolicyBuilder_Tests public class OperationRateLimitingPolicyBuilder_Tests
{ {
[Fact] [Fact]
public void Should_Build_Simple_Policy() public void Should_Build_Simple_Policy()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
options.AddPolicy("TestPolicy", policy => options.AddPolicy("TestPolicy", policy =>
{ {
policy.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5) policy.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5)
@ -22,14 +22,14 @@ public class OperationRateLimitPolicyBuilder_Tests
policy.Rules.Count.ShouldBe(1); policy.Rules.Count.ShouldBe(1);
policy.Rules[0].Duration.ShouldBe(TimeSpan.FromHours(1)); policy.Rules[0].Duration.ShouldBe(TimeSpan.FromHours(1));
policy.Rules[0].MaxCount.ShouldBe(5); policy.Rules[0].MaxCount.ShouldBe(5);
policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.Parameter); policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.Parameter);
policy.ErrorCode.ShouldBeNull(); policy.ErrorCode.ShouldBeNull();
} }
[Fact] [Fact]
public void Should_Build_Composite_Policy() public void Should_Build_Composite_Policy()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
options.AddPolicy("CompositePolicy", policy => options.AddPolicy("CompositePolicy", policy =>
{ {
policy.AddRule(rule => rule policy.AddRule(rule => rule
@ -45,16 +45,16 @@ public class OperationRateLimitPolicyBuilder_Tests
policy.Name.ShouldBe("CompositePolicy"); policy.Name.ShouldBe("CompositePolicy");
policy.Rules.Count.ShouldBe(2); policy.Rules.Count.ShouldBe(2);
policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.Parameter); policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.Parameter);
policy.Rules[0].MaxCount.ShouldBe(3); policy.Rules[0].MaxCount.ShouldBe(3);
policy.Rules[1].PartitionType.ShouldBe(OperationRateLimitPartitionType.CurrentUser); policy.Rules[1].PartitionType.ShouldBe(OperationRateLimitingPartitionType.CurrentUser);
policy.Rules[1].MaxCount.ShouldBe(10); policy.Rules[1].MaxCount.ShouldBe(10);
} }
[Fact] [Fact]
public void Should_Set_ErrorCode() public void Should_Set_ErrorCode()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
options.AddPolicy("ErrorPolicy", policy => options.AddPolicy("ErrorPolicy", policy =>
{ {
policy.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 2) policy.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 2)
@ -69,7 +69,7 @@ public class OperationRateLimitPolicyBuilder_Tests
[Fact] [Fact]
public void Should_Build_Custom_Partition() public void Should_Build_Custom_Partition()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
options.AddPolicy("CustomPolicy", policy => options.AddPolicy("CustomPolicy", policy =>
{ {
policy.AddRule(rule => rule policy.AddRule(rule => rule
@ -80,14 +80,14 @@ public class OperationRateLimitPolicyBuilder_Tests
var policy = options.Policies["CustomPolicy"]; var policy = options.Policies["CustomPolicy"];
policy.Rules.Count.ShouldBe(1); policy.Rules.Count.ShouldBe(1);
policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.Custom); policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.Custom);
policy.Rules[0].CustomPartitionKeyResolver.ShouldNotBeNull(); policy.Rules[0].CustomPartitionKeyResolver.ShouldNotBeNull();
} }
[Fact] [Fact]
public void Should_Support_All_Partition_Types() public void Should_Support_All_Partition_Types()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
options.AddPolicy("P1", p => p.WithFixedWindow(TimeSpan.FromHours(1), 1).PartitionByParameter()); options.AddPolicy("P1", p => p.WithFixedWindow(TimeSpan.FromHours(1), 1).PartitionByParameter());
options.AddPolicy("P2", p => p.WithFixedWindow(TimeSpan.FromHours(1), 1).PartitionByCurrentUser()); options.AddPolicy("P2", p => p.WithFixedWindow(TimeSpan.FromHours(1), 1).PartitionByCurrentUser());
@ -96,18 +96,18 @@ public class OperationRateLimitPolicyBuilder_Tests
options.AddPolicy("P5", p => p.WithFixedWindow(TimeSpan.FromHours(1), 1).PartitionByEmail()); options.AddPolicy("P5", p => p.WithFixedWindow(TimeSpan.FromHours(1), 1).PartitionByEmail());
options.AddPolicy("P6", p => p.WithFixedWindow(TimeSpan.FromHours(1), 1).PartitionByPhoneNumber()); options.AddPolicy("P6", p => p.WithFixedWindow(TimeSpan.FromHours(1), 1).PartitionByPhoneNumber());
options.Policies["P1"].Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.Parameter); options.Policies["P1"].Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.Parameter);
options.Policies["P2"].Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.CurrentUser); options.Policies["P2"].Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.CurrentUser);
options.Policies["P3"].Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.CurrentTenant); options.Policies["P3"].Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.CurrentTenant);
options.Policies["P4"].Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.ClientIp); options.Policies["P4"].Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.ClientIp);
options.Policies["P5"].Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.Email); options.Policies["P5"].Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.Email);
options.Policies["P6"].Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.PhoneNumber); options.Policies["P6"].Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.PhoneNumber);
} }
[Fact] [Fact]
public void Should_Throw_When_Policy_Has_No_Rules() public void Should_Throw_When_Policy_Has_No_Rules()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
var exception = Assert.Throws<AbpException>(() => var exception = Assert.Throws<AbpException>(() =>
{ {
@ -123,7 +123,7 @@ public class OperationRateLimitPolicyBuilder_Tests
[Fact] [Fact]
public void Should_Throw_When_WithFixedWindow_Without_PartitionBy() public void Should_Throw_When_WithFixedWindow_Without_PartitionBy()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
var exception = Assert.Throws<AbpException>(() => var exception = Assert.Throws<AbpException>(() =>
{ {
@ -140,7 +140,7 @@ public class OperationRateLimitPolicyBuilder_Tests
[Fact] [Fact]
public void Should_Throw_When_AddRule_Without_WithFixedWindow() public void Should_Throw_When_AddRule_Without_WithFixedWindow()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
var exception = Assert.Throws<AbpException>(() => var exception = Assert.Throws<AbpException>(() =>
{ {
@ -159,7 +159,7 @@ public class OperationRateLimitPolicyBuilder_Tests
[Fact] [Fact]
public void Should_Allow_MaxCount_Zero_For_Ban_Policy() public void Should_Allow_MaxCount_Zero_For_Ban_Policy()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
// maxCount=0 is a valid "ban" policy - always deny // maxCount=0 is a valid "ban" policy - always deny
options.AddPolicy("BanPolicy", policy => options.AddPolicy("BanPolicy", policy =>
@ -175,7 +175,7 @@ public class OperationRateLimitPolicyBuilder_Tests
[Fact] [Fact]
public void Should_Throw_When_AddRule_Without_PartitionBy() public void Should_Throw_When_AddRule_Without_PartitionBy()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
var exception = Assert.Throws<AbpException>(() => var exception = Assert.Throws<AbpException>(() =>
{ {
@ -193,7 +193,7 @@ public class OperationRateLimitPolicyBuilder_Tests
[Fact] [Fact]
public void Should_Throw_When_MaxCount_Is_Negative() public void Should_Throw_When_MaxCount_Is_Negative()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
var exception = Assert.Throws<AbpException>(() => var exception = Assert.Throws<AbpException>(() =>
{ {
@ -210,7 +210,7 @@ public class OperationRateLimitPolicyBuilder_Tests
[Fact] [Fact]
public void Should_Allow_Same_Rule_With_Different_MultiTenancy() public void Should_Allow_Same_Rule_With_Different_MultiTenancy()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
// Same Duration/MaxCount/PartitionType but different IsMultiTenant should be allowed // Same Duration/MaxCount/PartitionType but different IsMultiTenant should be allowed
options.AddPolicy("MultiTenancyPolicy", policy => options.AddPolicy("MultiTenancyPolicy", policy =>
@ -234,7 +234,7 @@ public class OperationRateLimitPolicyBuilder_Tests
[Fact] [Fact]
public void Should_Allow_Multiple_Custom_Partition_Rules() public void Should_Allow_Multiple_Custom_Partition_Rules()
{ {
var options = new AbpOperationRateLimitOptions(); var options = new AbpOperationRateLimitingOptions();
// Multiple custom partition rules with same Duration/MaxCount should be allowed // Multiple custom partition rules with same Duration/MaxCount should be allowed
// because they may use different key resolvers // because they may use different key resolvers
@ -251,7 +251,7 @@ public class OperationRateLimitPolicyBuilder_Tests
var policy = options.Policies["MultiCustomPolicy"]; var policy = options.Policies["MultiCustomPolicy"];
policy.Rules.Count.ShouldBe(2); policy.Rules.Count.ShouldBe(2);
policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.Custom); policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitingPartitionType.Custom);
policy.Rules[1].PartitionType.ShouldBe(OperationRateLimitPartitionType.Custom); policy.Rules[1].PartitionType.ShouldBe(OperationRateLimitingPartitionType.Custom);
} }
} }

4
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/OperationRateLimitTestBase.cs → framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingTestBase.cs

@ -1,8 +1,8 @@
using Volo.Abp.Testing; using Volo.Abp.Testing;
namespace Volo.Abp.OperationRateLimit; namespace Volo.Abp.OperationRateLimiting;
public class OperationRateLimitTestBase : AbpIntegratedTest<AbpOperationRateLimitTestModule> public class OperationRateLimitingTestBase : AbpIntegratedTest<AbpOperationRateLimitingTestModule>
{ {
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{ {
Loading…
Cancel
Save