w4c-workflows-api/w4c-workflows-api.Tests/WorkflowQuotaServiceTests.cs
2026-09-12 01:02:46 +03:00

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);
}
}