feat(daily-prep): stream prep output via PrepStarted/PrepLine/PrepFinished
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,11 +19,15 @@ public class PrimeRunnerTests : IDisposable
|
||||
{
|
||||
private readonly TimeSpan _delay;
|
||||
private readonly int _exitCode;
|
||||
private readonly string[] _emitLines;
|
||||
private readonly string? _result;
|
||||
|
||||
public FakeClaudeProcess(TimeSpan delay = default, int exitCode = 0)
|
||||
public FakeClaudeProcess(TimeSpan delay = default, int exitCode = 0, string[]? emitLines = null, string? result = null)
|
||||
{
|
||||
_delay = delay;
|
||||
_exitCode = exitCode;
|
||||
_emitLines = emitLines ?? [];
|
||||
_result = result;
|
||||
}
|
||||
|
||||
public async Task<RunResult> RunAsync(
|
||||
@@ -36,16 +40,36 @@ public class PrimeRunnerTests : IDisposable
|
||||
if (_delay > TimeSpan.Zero)
|
||||
await Task.Delay(_delay, ct);
|
||||
|
||||
return new RunResult { ExitCode = _exitCode, ResultMarkdown = _exitCode == 0 ? "ok" : null };
|
||||
foreach (var line in _emitLines)
|
||||
await onStdoutLine(line);
|
||||
|
||||
return new RunResult { ExitCode = _exitCode, ResultMarkdown = _result ?? (_exitCode == 0 ? "ok" : null) };
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingPrimeBroadcaster : IPrimeBroadcaster
|
||||
{
|
||||
public int StartedCount { get; private set; }
|
||||
public List<string> Lines { get; } = [];
|
||||
public List<bool> FinishedResults { get; } = [];
|
||||
|
||||
public Task PrimeFiredAsync(Guid scheduleId, bool success, string message, DateTimeOffset firedAt) => Task.CompletedTask;
|
||||
|
||||
public Task PrepStartedAsync() { StartedCount++; return Task.CompletedTask; }
|
||||
public Task PrepLineAsync(string line) { Lines.Add(line); return Task.CompletedTask; }
|
||||
public Task PrepFinishedAsync(bool success) { FinishedResults.Add(success); return Task.CompletedTask; }
|
||||
}
|
||||
|
||||
private PrimeRunner NewRunner(TimeSpan claudeDelay = default, int exitCode = 0) =>
|
||||
NewRunner(new FakeClaudeProcess(claudeDelay, exitCode), new RecordingPrimeBroadcaster());
|
||||
|
||||
private PrimeRunner NewRunner(FakeClaudeProcess claude, IPrimeBroadcaster broadcaster) =>
|
||||
new PrimeRunner(
|
||||
new FakeClaudeProcess(claudeDelay, exitCode),
|
||||
claude,
|
||||
_db.CreateFactory(),
|
||||
new FakeClock(),
|
||||
NullLogger<PrimeRunner>.Instance);
|
||||
NullLogger<PrimeRunner>.Instance,
|
||||
broadcaster);
|
||||
|
||||
private static PrimeScheduleDto DefaultSchedule() =>
|
||||
new(Guid.Empty, 0, TimeSpan.Zero, true, null, null);
|
||||
@@ -83,4 +107,21 @@ public class PrimeRunnerTests : IDisposable
|
||||
Assert.Contains("already running", second.Message, StringComparison.OrdinalIgnoreCase);
|
||||
await first;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FireAsync_streams_started_lines_and_finished()
|
||||
{
|
||||
var broadcaster = new RecordingPrimeBroadcaster();
|
||||
var claude = new FakeClaudeProcess(emitLines: ["{\"a\":1}", "{\"b\":2}"], exitCode: 0, result: "ok");
|
||||
var runner = NewRunner(claude, broadcaster);
|
||||
var schedule = new PrimeScheduleDto(Guid.Empty, 0, TimeSpan.Zero, true, null, null);
|
||||
|
||||
var outcome = await runner.FireAsync(schedule, CancellationToken.None);
|
||||
|
||||
Assert.True(outcome.Success);
|
||||
Assert.Equal(1, broadcaster.StartedCount);
|
||||
Assert.Equal(new[] { "{\"a\":1}", "{\"b\":2}" }, broadcaster.Lines);
|
||||
Assert.Single(broadcaster.FinishedResults);
|
||||
Assert.True(broadcaster.FinishedResults[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ public class PrimeSchedulerTests : IDisposable
|
||||
Calls.Add((id, ok, msg));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task PrepStartedAsync() => Task.CompletedTask;
|
||||
public Task PrepLineAsync(string line) => Task.CompletedTask;
|
||||
public Task PrepFinishedAsync(bool success) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user