using ClawdDotNet.App.Services; using ClawdDotNet.Core.Config; using ClawdDotNet.Core.Storage; using ClawdDotNet.Core.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Shouldly; namespace ClawdDotNet.App.Tests.Services; /// /// Das Taskboard fuer die Oberflaechen (#594) mit den Regeln aus #597 (Termin pruefen) und /// #598 (Hinweis auf wachsenden Kontext). /// public sealed class TaskAdministrationTests : IDisposable { private readonly string _dir = Path.Combine(Path.GetTempPath(), "clawd-tests", "tasks-" + Guid.NewGuid().ToString("N")[..8]); private readonly SqliteStorage _storage; private readonly TaskboardService _board; private readonly List _agents = [TestHost.Agent("nachrichten"), TestHost.Agent("wetterdienst")]; private readonly TaskAdministration _admin; public TaskAdministrationTests() { _storage = new SqliteStorage(Path.Combine(_dir, "state.db")); _board = new TaskboardService(new SqliteTaskRepository(_storage), Path.Combine(_dir, "tasks")); _admin = new TaskAdministration(_board, null, null, null, null, () => _agents, NullLogger.Instance); } public void Dispose() { ClawdDotNet.Core.Storage.SqliteStorage.ReleasePooledConnections(_dir); try { Directory.Delete(_dir, recursive: true); } catch { /* Aufraeumen ist Nebensache */ } } private async Task Einzige() => (await _board.ListAsync(new TaskQuery(), default)).Single(); [Fact] public async Task Ein_Termin_der_nie_feuern_kann_wird_nicht_gespeichert() { var ergebnis = await _admin.CreateAsync(new TaskInput( "Nachrichten-Digest", Assignee: "@new:nachrichten", WhenKind: "cron", WhenValue: "0 7-22/x * * *", TimeZone: "Europe/Berlin"), default); ergebnis.Ok.ShouldBeFalse(); ergebnis.Message.ShouldContain("keine Zahl"); (await _board.ListAsync(new TaskQuery(), default)).ShouldBeEmpty(); } [Fact] public async Task Wiederkehrend_im_Chatverlauf_wird_angelegt_aber_mit_Hinweis() { var ergebnis = await _admin.CreateAsync(new TaskInput( "Nachrichten-Digest", Assignee: "@nachrichten", WhenKind: "cron", WhenValue: "0 7-22/3 * * *", TimeZone: "Europe/Berlin"), default); ergebnis.Ok.ShouldBeTrue(); ergebnis.Message.ShouldContain("@new:nachrichten"); } [Theory] [InlineData("@new:gibtsnicht")] [InlineData("@gibtsnicht")] [InlineData("")] public async Task Ohne_gueltige_Zuordnung_entsteht_keine_Aufgabe(string assignee) => (await _admin.CreateAsync(new TaskInput("X", Assignee: assignee), default)).Ok.ShouldBeFalse(); [Fact] public async Task Pausieren_und_Aktivieren_setzen_backlog_und_todo() { await _admin.CreateAsync(new TaskInput("Wetter", Assignee: "@new:wetterdienst", WhenKind: "cron", WhenValue: "5 * * * *"), default); var id = (await Einzige()).Id; (await _admin.SetEnabledAsync(id, false, default)).Ok.ShouldBeTrue(); (await Einzige()).Status.ShouldBe(TaskItemStatus.Backlog); (await _admin.SetEnabledAsync(id, true, default)).Ok.ShouldBeTrue(); (await Einzige()).Status.ShouldBe(TaskItemStatus.Todo); } [Fact] public async Task Die_Liste_nennt_den_naechsten_Lauf() { await _admin.CreateAsync(new TaskInput("Wetter", Assignee: "@new:wetterdienst", WhenKind: "cron", WhenValue: "5 * * * *", TimeZone: "Europe/Berlin"), default); var zeile = (await _admin.ListAsync(50, default))!.Single(); zeile.NextRunUtc.ShouldNotBeNull(); zeile.Hinweis.ShouldBeNull(); zeile.LastRun.ShouldBeNull(); } [Fact] public async Task Bearbeiten_aendert_den_Termin() { await _admin.CreateAsync(new TaskInput("Wetter", Assignee: "@new:wetterdienst", WhenKind: "cron", WhenValue: "5 * * * *"), default); var id = (await Einzige()).Id; var ergebnis = await _admin.UpdateAsync(id, new TaskInput("Wetter", Assignee: "@new:wetterdienst", WhenKind: "every", WhenValue: "2h"), default); ergebnis.Ok.ShouldBeTrue(); (await Einzige()).When!.Kind.ShouldBe(TaskWhenKind.Every); } [Fact] public async Task Sofortlauf_ohne_Scanner_wird_abgelehnt_statt_still_nichts_zu_tun() { await _admin.CreateAsync(new TaskInput("Wetter", Assignee: "@new:wetterdienst"), default); var ergebnis = await _admin.RunNowAsync((await Einzige()).Id, default); ergebnis.Ok.ShouldBeFalse(); ergebnis.Message.ShouldContain("Scanner"); } [Fact] public async Task Ohne_Taskboard_liefert_die_Liste_null() => (await new TaskAdministration(null, null, null, null, null, () => _agents, NullLogger.Instance) .ListAsync(10, default)).ShouldBeNull(); }