using System.Text.Json;
using ClawdDotNet.Core.Config;
using ClawdDotNet.Core.Organisation;
using ClawdDotNet.Core.Storage;
using ClawdDotNet.Core.Tasks;
using ClawdDotNet.Core.Tools;
using ClawdDotNet.Tools.Taskboard;
using ClawdDotNet.Tools.Tests.FileRW;
using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;
namespace ClawdDotNet.Tools.Tests.Taskboard;
///
/// #683: A task for another agent is a way of reaching it. The address book that holds for
/// messages holds here too — otherwise it could be walked around with task_create.
///
public sealed class AssignmentReachTests : IDisposable
{
private readonly string _directory;
private readonly SqliteStorage _storage;
private readonly SqliteTaskRepository _repo;
private readonly TaskboardTool _tool = new();
private readonly OrgChart _org = new(
[
new UnitConfig { Id = "amazon", Name = "Amazon", LeaderAgentId = "amazon-lead" },
new UnitConfig { Id = "accounting", Name = "Accounting", LeaderAgentId = "accounting-lead" }
],
[
new AgentConfig { AgentId = "amazon-lead", UnitId = "amazon" },
new AgentConfig { AgentId = "ads", UnitId = "amazon" },
new AgentConfig { AgentId = "accounting-lead", UnitId = "accounting" },
new AgentConfig { AgentId = "bookkeeper", UnitId = "accounting" }
]);
public AssignmentReachTests()
{
_directory = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
_storage = new SqliteStorage(Path.Combine(_directory, "state.db"));
_repo = new SqliteTaskRepository(_storage);
}
public void Dispose()
{
Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools();
try { Directory.Delete(_directory, recursive: true); }
catch { /* cleanup is secondary */ }
}
private Task Run(string agentId, string json)
{
var context = new AgentToolContext(
agentId, "inst", new Dictionary(), new NullStateStore(), NullLogger.Instance,
CancellationToken.None, SharedWorkspacePath: Path.Combine(_directory, "SharedWorkspace"), Tasks: _repo)
{
Organisation = _org
};
return _tool.ExecuteAsync(JsonDocument.Parse(json).RootElement, context, CancellationToken.None);
}
[Fact]
public async Task A_task_for_an_agent_outside_the_address_book_is_refused()
{
var result = await Run("ads", """{"action":"task_create","title":"Rechnung buchen","assignee":"@bookkeeper"}""");
result.Success.ShouldBeFalse();
result.ErrorMessage.ShouldNotBeNull().ShouldContain("not in your address book");
result.ErrorMessage.ShouldContain("amazon-lead");
}
[Fact]
public async Task A_fresh_run_of_an_agent_outside_is_refused_too()
{
var result = await Run("ads", """{"action":"task_create","title":"Rechnung buchen","assignee":"@new:bookkeeper"}""");
result.Success.ShouldBeFalse();
}
[Fact]
public async Task A_task_for_the_own_lead_or_the_human_goes_through()
{
(await Run("ads", """{"action":"task_create","title":"Frage an die Leitung","assignee":"@amazon-lead"}""")).Success.ShouldBeTrue();
(await Run("ads", """{"action":"task_create","title":"Frage an den Menschen","assignee":"@human"}""")).Success.ShouldBeTrue();
(await Run("ads", """{"action":"task_create","title":"Eigene Aufgabe","assignee":"@ads"}""")).Success.ShouldBeTrue();
}
[Fact]
public async Task Reassigning_is_held_to_the_same_rule()
{
var created = await Run("ads", """{"action":"task_create","title":"Eigene Aufgabe","assignee":"@ads"}""");
var id = created.Content!.Split(' ', ':', '\n').First(w => w.StartsWith("t-"));
var result = await Run("ads", $$"""{"action":"task_update","id":"{{id}}","assignee":"@bookkeeper"}""");
result.Success.ShouldBeFalse();
}
}