using w4c_workflows.Services.Quota; using Xunit; namespace w4c_workflows.Tests; /// /// 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. /// [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); } }