106 lines
3.8 KiB
C#
106 lines
3.8 KiB
C#
|
|
using w4c_workflows.Services.Quota;
|
||
|
|
using Xunit;
|
||
|
|
|
||
|
|
namespace w4c_workflows.Tests;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Unit/integration tests for the per-tenant execution quota counter. Each test
|
||
|
|
/// uses a unique tenant id because the Postgres container is shared per collection.
|
||
|
|
/// </summary>
|
||
|
|
[Collection("WorkflowsPostgres")]
|
||
|
|
public class WorkflowQuotaServiceTests
|
||
|
|
{
|
||
|
|
private readonly WorkflowsPostgresFixture _fixture;
|
||
|
|
|
||
|
|
public WorkflowQuotaServiceTests(WorkflowsPostgresFixture fixture)
|
||
|
|
{
|
||
|
|
_fixture = fixture;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string Tenant() => "t" + Guid.NewGuid().ToString("N")[..12];
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Reserve_increments_usage_and_exhausts_the_limit()
|
||
|
|
{
|
||
|
|
var tenantId = Tenant();
|
||
|
|
await using var db = _fixture.CreateContext();
|
||
|
|
var quota = QuotaTestData.Service(db, new WorkflowQuotaOptions { RunsPerMonth = 2 });
|
||
|
|
|
||
|
|
Assert.True(await quota.TryReserveAsync(tenantId));
|
||
|
|
Assert.True(await quota.TryReserveAsync(tenantId));
|
||
|
|
|
||
|
|
var used = await quota.GetAsync(tenantId);
|
||
|
|
Assert.Equal(2, used.Used);
|
||
|
|
Assert.Equal(2, used.Limit);
|
||
|
|
Assert.Equal(0, used.Remaining);
|
||
|
|
Assert.True(used.Exceeded);
|
||
|
|
Assert.True(used.Enforced);
|
||
|
|
|
||
|
|
// The next reservation is refused and does NOT bump the counter.
|
||
|
|
Assert.False(await quota.TryReserveAsync(tenantId));
|
||
|
|
Assert.Equal(2, (await quota.GetAsync(tenantId)).Used);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Reserve_is_unlimited_when_the_limit_is_zero()
|
||
|
|
{
|
||
|
|
var tenantId = Tenant();
|
||
|
|
await using var db = _fixture.CreateContext();
|
||
|
|
var quota = QuotaTestData.Service(db, new WorkflowQuotaOptions { RunsPerMonth = 0 });
|
||
|
|
|
||
|
|
for (var i = 0; i < 5; i++)
|
||
|
|
Assert.True(await quota.TryReserveAsync(tenantId));
|
||
|
|
|
||
|
|
var used = await quota.GetAsync(tenantId);
|
||
|
|
Assert.Equal(5, used.Used);
|
||
|
|
Assert.False(used.Enforced);
|
||
|
|
Assert.False(used.Exceeded);
|
||
|
|
Assert.Equal(-1, used.Remaining);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Metering_without_enforcement_counts_but_never_blocks()
|
||
|
|
{
|
||
|
|
var tenantId = Tenant();
|
||
|
|
await using var db = _fixture.CreateContext();
|
||
|
|
var quota = QuotaTestData.Service(db, new WorkflowQuotaOptions { Enforced = false, RunsPerMonth = 1 });
|
||
|
|
|
||
|
|
Assert.True(await quota.TryReserveAsync(tenantId));
|
||
|
|
Assert.True(await quota.TryReserveAsync(tenantId));
|
||
|
|
|
||
|
|
var used = await quota.GetAsync(tenantId);
|
||
|
|
Assert.Equal(2, used.Used);
|
||
|
|
Assert.False(used.Exceeded);
|
||
|
|
Assert.False(used.Enforced);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Reset_zeroes_the_current_period_counter()
|
||
|
|
{
|
||
|
|
var tenantId = Tenant();
|
||
|
|
await using var db = _fixture.CreateContext();
|
||
|
|
var quota = QuotaTestData.Service(db, new WorkflowQuotaOptions { RunsPerMonth = 10 });
|
||
|
|
|
||
|
|
Assert.True(await quota.TryReserveAsync(tenantId));
|
||
|
|
Assert.True(await quota.TryReserveAsync(tenantId));
|
||
|
|
Assert.Equal(2, (await quota.GetAsync(tenantId)).Used);
|
||
|
|
|
||
|
|
var afterReset = await quota.ResetAsync(tenantId);
|
||
|
|
Assert.Equal(0, afterReset.Used);
|
||
|
|
Assert.Equal(0, (await quota.GetAsync(tenantId)).Used);
|
||
|
|
// A reset row is re-creatable: the next run starts a fresh count.
|
||
|
|
Assert.True(await quota.TryReserveAsync(tenantId));
|
||
|
|
Assert.Equal(1, (await quota.GetAsync(tenantId)).Used);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void PeriodFor_is_the_containing_calendar_month_in_utc()
|
||
|
|
{
|
||
|
|
var (start, end) = WorkflowQuotaService.PeriodFor(new DateTime(2026, 9, 11, 23, 59, 59, DateTimeKind.Utc));
|
||
|
|
|
||
|
|
Assert.Equal(new DateTime(2026, 9, 1, 0, 0, 0, DateTimeKind.Utc), start);
|
||
|
|
Assert.Equal(new DateTime(2026, 10, 1, 0, 0, 0, DateTimeKind.Utc), end);
|
||
|
|
Assert.Equal(DateTimeKind.Utc, start.Kind);
|
||
|
|
}
|
||
|
|
}
|