using ClaudeDo.Data; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Report; using ClaudeDo.Worker.Report.Interfaces; using ClaudeDo.Worker.Runner; using ClaudeDo.Worker.Tests.Infrastructure; using Microsoft.Extensions.Logging.Abstractions; namespace ClaudeDo.Worker.Tests.Report; public class WeekReportServiceTests : IDisposable { private readonly DbFixture _db = new(); public void Dispose() => _db.Dispose(); private static readonly DateOnly Start = new(2026, 5, 28); private static readonly DateOnly End = new(2026, 6, 3); private sealed class FakeReader : IClaudeHistoryReader { public IReadOnlyList Result = Array.Empty(); public Task> ReadAsync( DateOnly s, DateOnly e, IReadOnlyList ex, CancellationToken ct) => Task.FromResult(Result); } private sealed class FakeClaude : IClaudeProcess { public int Calls; public RunResult Next = new() { ExitCode = 0, ResultMarkdown = "## Bericht" }; public Task RunAsync(string args, string prompt, string wd, Func onLine, CancellationToken ct) { Calls++; return Task.FromResult(Next); } } private WeekReportService Make(FakeReader reader, FakeClaude claude) => new(reader, _db.CreateFactory(), claude, NullLogger.Instance); [Fact] public async Task EmptyWindow_ProducesNoActivityReport_WithoutCallingClaude() { var claude = new FakeClaude(); var svc = Make(new FakeReader(), claude); var md = await svc.GenerateAsync(Start, End); Assert.Equal(0, claude.Calls); Assert.Contains("Keine Aktivität", md); using var ctx = _db.CreateContext(); Assert.NotNull(await new WeekReportRepository(ctx).GetByRangeAsync(Start, End)); } [Fact] public async Task SuccessPath_StoresAndReturnsClaudeMarkdown() { var repo = new RepoActivity { RepoPath = @"C:\Dev\App" }; var day = new DayActivity { Date = new DateOnly(2026, 6, 1) }; day.Prompts.Add("Add login"); repo.Days.Add(day); var claude = new FakeClaude { Next = new RunResult { ExitCode = 0, ResultMarkdown = "## Bericht\n- Habe Login umgesetzt" } }; var svc = Make(new FakeReader { Result = new[] { repo } }, claude); var md = await svc.GenerateAsync(Start, End); Assert.Equal(1, claude.Calls); Assert.Contains("Habe Login umgesetzt", md); Assert.Equal(md, await svc.GetStoredAsync(Start, End)); } [Fact] public async Task ClaudeFailure_Throws_AndDoesNotStore() { var repo = new RepoActivity { RepoPath = @"C:\Dev\App" }; var day = new DayActivity { Date = new DateOnly(2026, 6, 1) }; day.Prompts.Add("x"); repo.Days.Add(day); var claude = new FakeClaude { Next = new RunResult { ExitCode = 1, ErrorMarkdown = "boom" } }; var svc = Make(new FakeReader { Result = new[] { repo } }, claude); await Assert.ThrowsAsync(() => svc.GenerateAsync(Start, End)); Assert.Null(await svc.GetStoredAsync(Start, End)); } }