feat(claude-do): Worker: UsageGate — Queue ab Schwelle pausieren
> **Stand 2026-08-05 (List-Handler):** Der Roadblock aus dem letzten Lauf ist erledigt. Beide Voraussetzungen sind jetzt auf `main` gemerged: die `app_settings`-Schwellen `UsageGateFiveHourPct`/`UsageGateSevenDayPct` (Merge-Commit `b1efcdc`) und `UsageState`/`IUsageClient`/`UsageMonitorService` unter `src/ClaudeDo.Worker/Usage/` (Merge-Commit `b126a21`). Dein Worktree ist frisch von diesem `main` ClaudeDo-Task: 06a7cc32-6ab7-4758-98f4-bee77149b2bf
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.Tests.Infrastructure;
|
||||
using ClaudeDo.Worker.Usage;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.Usage;
|
||||
|
||||
public sealed class UsageGateTests : IDisposable
|
||||
{
|
||||
private readonly DbFixture _db = new();
|
||||
|
||||
public void Dispose() => _db.Dispose();
|
||||
|
||||
private async Task SetThresholdsAsync(int fiveHourPct, int sevenDayPct)
|
||||
{
|
||||
using var ctx = _db.CreateContext();
|
||||
var repo = new AppSettingsRepository(ctx);
|
||||
var settings = await repo.GetAsync();
|
||||
settings.UsageGateFiveHourPct = fiveHourPct;
|
||||
settings.UsageGateSevenDayPct = sevenDayPct;
|
||||
await repo.UpdateAsync(settings);
|
||||
}
|
||||
|
||||
private UsageGate CreateGate(UsageState state) =>
|
||||
new(_db.CreateFactory(), state, NullLogger<UsageGate>.Instance);
|
||||
|
||||
private static UsageState StateWithSnapshot(double fiveHourPct, double sevenDayPct)
|
||||
{
|
||||
var state = new UsageState();
|
||||
state.ReportSuccess(new UsageSnapshot(
|
||||
new UsageBucket(fiveHourPct, null),
|
||||
new UsageBucket(sevenDayPct, null),
|
||||
Array.Empty<UsageLimitRow>(),
|
||||
DateTime.UtcNow));
|
||||
return state;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BothUnderThreshold_NotBlocked()
|
||||
{
|
||||
await SetThresholdsAsync(80, 90);
|
||||
var decision = await CreateGate(StateWithSnapshot(50, 60)).EvaluateAsync();
|
||||
Assert.False(decision.IsBlocked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FiveHourAtOrOverThreshold_Blocked()
|
||||
{
|
||||
await SetThresholdsAsync(80, 90);
|
||||
var decision = await CreateGate(StateWithSnapshot(85, 60)).EvaluateAsync();
|
||||
Assert.True(decision.IsBlocked);
|
||||
Assert.Contains("5h", decision.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SevenDayAtOrOverThreshold_Blocked()
|
||||
{
|
||||
await SetThresholdsAsync(80, 90);
|
||||
var decision = await CreateGate(StateWithSnapshot(50, 95)).EvaluateAsync();
|
||||
Assert.True(decision.IsBlocked);
|
||||
Assert.Contains("7d", decision.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExactlyOnThreshold_Blocked()
|
||||
{
|
||||
await SetThresholdsAsync(80, 90);
|
||||
var decision = await CreateGate(StateWithSnapshot(80, 60)).EvaluateAsync();
|
||||
Assert.True(decision.IsBlocked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThresholdZero_ThatBucketNeverGates()
|
||||
{
|
||||
await SetThresholdsAsync(0, 90);
|
||||
var decision = await CreateGate(StateWithSnapshot(100, 60)).EvaluateAsync();
|
||||
Assert.False(decision.IsBlocked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BothThresholdsZero_AlwaysFree()
|
||||
{
|
||||
await SetThresholdsAsync(0, 0);
|
||||
var decision = await CreateGate(StateWithSnapshot(100, 100)).EvaluateAsync();
|
||||
Assert.False(decision.IsBlocked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NoSnapshotYet_FailsOpen()
|
||||
{
|
||||
await SetThresholdsAsync(80, 90);
|
||||
var decision = await CreateGate(new UsageState()).EvaluateAsync();
|
||||
Assert.False(decision.IsBlocked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LastPollFailed_FailsOpen()
|
||||
{
|
||||
await SetThresholdsAsync(80, 90);
|
||||
var state = StateWithSnapshot(95, 95);
|
||||
state.ReportFailure("boom", DateTime.UtcNow);
|
||||
var decision = await CreateGate(state).EvaluateAsync();
|
||||
Assert.False(decision.IsBlocked);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user