using ClawdDotNet.Core.Engine; using ClawdDotNet.Core.Staging; using ClawdDotNet.Core.Tasks; using ClawdDotNet.Web.Contracts; using ClawdDotNet.Web.Endpoints; using Shouldly; namespace ClawdDotNet.Web.Tests; /// /// Findings from reviewing the web UI in test operation (#572): #591 (field names) and /// #536 (direction of the A2A answers). /// public sealed class TestOperationWebTests { // ─── #591 event stream and transfer types ─── [Fact] public void Log_lines_in_the_event_stream_come_in_camelCase() { var evt = EventEndpoints.Format("log", new LogLineDto("Startup", "INF", "ClawdDotNet startet")); evt.ShouldStartWith("event: log\n"); evt.ShouldContain("\"module\":\"Startup\""); evt.ShouldContain("\"level\":\"INF\""); evt.ShouldContain("\"text\":\"ClawdDotNet startet\""); evt.ShouldNotContain("\"Text\"", Case.Sensitive); } [Fact] public void Approval_carries_its_time() { var created = new DateTime(2026, 9, 14, 10, 18, 53, DateTimeKind.Utc); var dto = new StagedCall { Id = 1, AgentId = "hausmeister", Tool = "FileRW", Action = "delete", ArgumentsJson = "{\"path\":\"betrieb/probe-loeschung.txt\"}", CreatedAt = created }.ToDto(); dto.CreatedAt.ShouldBe(created); dto.Tool.ShouldBe("FileRW"); dto.Action.ShouldBe("delete"); } [Fact] public void Task_carries_its_schedule() { var dto = new TaskItem { Id = "t-a66932", Title = "Wetter Berlin stuendlich", Assignee = "@new:wetterdienst", When = new TaskWhen { Kind = TaskWhenKind.Cron, Value = "5 * * * *", TimeZone = "Europe/Berlin" } }.ToDto(); dto.When.ShouldNotBeNull(); dto.When.Kind.ShouldBe("cron"); dto.When.Value.ShouldBe("5 * * * *"); dto.When.TimeZone.ShouldBe("Europe/Berlin"); dto.Assignee.ShouldBe("@new:wetterdienst"); } [Fact] public void One_off_task_has_no_schedule() => new TaskItem { Id = "t-1", Title = "Einrichtung" }.ToDto().When.ShouldBeNull(); // ─── #536 sender and recipient ─── private static ChatEntry Request(string fromName, string fromId, string text, int minute) => new("user", $"[Nachricht von Agent \"{fromName}\" ({fromId})]\n\n{text}", new DateTime(2026, 9, 14, 12, minute, 0), ChatSource.AgentComm); private static ChatEntry Answer(string text, int minute) => new("assistant", text, new DateTime(2026, 9, 14, 12, minute, 30), ChatSource.AgentComm); [Fact] public void Answer_goes_from_the_responder_to_the_asker() { var list = AgentCommunicationBuilder.Build( [ ("redaktion", "Redaktion", (IEnumerable) [ Request("Hausmeister", "hausmeister", "Testnachricht", 48), Answer("Empfang bestaetigt", 48) ]) ]); list.Count.ShouldBe(2); var request = list[0]; request.SourceAgentId.ShouldBe("hausmeister"); request.TargetAgentId.ShouldBe("redaktion"); request.Content.ShouldBe("Testnachricht"); var answer = list[1]; answer.SourceAgentId.ShouldBe("redaktion"); answer.TargetAgentId.ShouldBe("hausmeister", "vorher stand hier der Antwortende selbst"); answer.TargetAgentName.ShouldBe("Hausmeister"); } [Fact] public void Only_agent_communication_is_recorded() { var list = AgentCommunicationBuilder.Build( [ ("hausmeister", "Hausmeister", (IEnumerable) [ new ChatEntry("user", "Hey, wie gehts?", DateTime.Now, ChatSource.WebUi), new ChatEntry("assistant", "Moin!", DateTime.Now, ChatSource.WebUi) ]) ]); list.ShouldBeEmpty(); } [Theory] [InlineData("[Nachricht von Agent \"Hausmeister\" ohne Klammer]\n\nText")] [InlineData("[Nachricht von Agent \"Bo\"b\" (bob)]\n\nText")] [InlineData("[Nachricht von Agent \"")] public void Reading_the_sender_does_not_throw_on_unexpected_form(string content) => Should.NotThrow(() => AgentCommunicationBuilder.ReadSender(content)); [Fact] public void Reading_the_sender_returns_id_name_and_text() { var (id, name, text) = AgentCommunicationBuilder.ReadSender( "[Nachricht von Agent \"Hausmeister\" (hausmeister)]\n\nHallo Redaktion!"); id.ShouldBe("hausmeister"); name.ShouldBe("Hausmeister"); text.ShouldBe("Hallo Redaktion!"); } }