Mit Roslyn umbenannt (Verweise folgen, Zeichenketten bleiben unberuehrt). Nicht umbenannt, weil gespeichert oder ueber die API gebunden (Stufe 4e): Properties, Record-Parameter, Enum-Member sowie die Parameter der Endpunkt-Handler (Routen-/Query-Bindung ueber den Namen). Die JSON-Felder anonymer Objekte bleiben ausdruecklich benannt (JsonFilter: pfad). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
3.5 KiB
C#
94 lines
3.5 KiB
C#
using System.Text.Json;
|
|
using ClawdDotNet.Core.State;
|
|
using ClawdDotNet.Core.Tasks;
|
|
using ClawdDotNet.Core.Tests.Infrastructure;
|
|
using ClawdDotNet.Core.Tools;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Core.Tests.Tasks;
|
|
|
|
/// <summary>
|
|
/// The tool_job dispatch — replaces the ToolJobScheduler. A due poll task ticks an
|
|
/// <see cref="IToolJobProvider"/> and only wakes the agent if the tick reports something.
|
|
/// </summary>
|
|
public sealed class ToolJobDispatchTests
|
|
{
|
|
private static TaskItem PollTask(string agent = "agent-a") => new()
|
|
{
|
|
Id = "tj-1",
|
|
Title = "Poll",
|
|
Type = TaskItemType.ToolJob,
|
|
ToolName = "PollTool",
|
|
JobTypeId = "poll",
|
|
Assignee = "@" + agent
|
|
};
|
|
|
|
private static EngineTaskDispatcher Dispatcher(EngineFixture fixture)
|
|
=> new(fixture.Engine, () => fixture.Agents, "test-instance",
|
|
fixture.Registry, fixture.StateStore, NullLoggerFactory.Instance);
|
|
|
|
[Fact]
|
|
public async Task A_poll_without_a_hit_does_not_wake_the_agent()
|
|
{
|
|
var tool = new FakeJobTool(ToolJobResult.NoAction("nichts Neues"));
|
|
var fixture = new EngineFixture().WithTool(tool);
|
|
fixture.AddAgent("agent-a", "PollTool");
|
|
|
|
var ok = await Dispatcher(fixture).DispatchAsync(PollTask(), default);
|
|
|
|
ok.ShouldBe(DispatchOutcome.Erledigt, "ein Poll ohne Fund ist kein Fehler");
|
|
tool.Ticks.ShouldBe(1);
|
|
fixture.Client.ReceivedRequests.ShouldBeEmpty("kein Agent-Lauf, wenn nichts zu wecken war");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_poll_with_a_hit_wakes_the_agent()
|
|
{
|
|
var tool = new FakeJobTool(ToolJobResult.Wake("Neue Nachricht eingetroffen"));
|
|
var fixture = new EngineFixture().WithTool(tool);
|
|
fixture.AddAgent("agent-a", "PollTool");
|
|
fixture.Client.RespondsWithText("verarbeitet");
|
|
|
|
var ok = await Dispatcher(fixture).DispatchAsync(PollTask(), default);
|
|
|
|
ok.ShouldBe(DispatchOutcome.Erledigt);
|
|
tool.Ticks.ShouldBe(1);
|
|
fixture.Client.ReceivedRequests.ShouldNotBeEmpty("der Agent wurde geweckt");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task An_unknown_poll_tool_reports_an_error()
|
|
{
|
|
var fixture = new EngineFixture(); // PollTool not registered
|
|
fixture.AddAgent("agent-a");
|
|
|
|
(await Dispatcher(fixture).DispatchAsync(PollTask(), default)).ShouldBe(DispatchOutcome.Gescheitert);
|
|
}
|
|
|
|
/// <summary>A tool that is at the same time a poll provider — counts ticks, delivers a fixed result.</summary>
|
|
private sealed class FakeJobTool(ToolJobResult result) : IAgentTool, IToolJobProvider
|
|
{
|
|
public int Ticks { get; private set; }
|
|
|
|
public string Name => "PollTool";
|
|
public string Description => "Test-Poll";
|
|
public JsonElement InputSchema { get; } =
|
|
JsonDocument.Parse("""{ "type": "object" }""").RootElement.Clone();
|
|
|
|
public Task<ToolResult> ExecuteAsync(JsonElement input, AgentToolContext context, CancellationToken ct)
|
|
=> Task.FromResult(ToolResult.Ok("ok"));
|
|
|
|
public IReadOnlyList<ToolJobDefinition> GetJobDefinitions() => [];
|
|
|
|
public Task<ToolJobResult> ExecuteJobAsync(
|
|
string jobTypeId, IReadOnlyDictionary<string, object?> toolConfig, IStateStore stateStore,
|
|
ILogger logger, CancellationToken ct, string? agentId = null, string? workspacePath = null)
|
|
{
|
|
Ticks++;
|
|
return Task.FromResult(result);
|
|
}
|
|
}
|
|
}
|