using System.Text.Json; using ClawdDotNet.Core.Tools; using ClawdDotNet.Tools.AgentEditor; using ClawdDotNet.Tools.FileRW; using ClawdDotNet.Tools.Tests.FileRW; using Microsoft.Extensions.Logging.Abstractions; using Shouldly; namespace ClawdDotNet.Tools.Tests.PreCheck; /// /// The pre-check of the tools (#579) and the hardening of the AgentEditor (#205): what the /// tool would refuse anyway should not even get into the approval list. /// public sealed class PreCheckTests : IDisposable { private readonly string _root = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N")); private readonly string _agents; private readonly string _shared; public PreCheckTests() { _agents = Path.Combine(_root, "Agents"); _shared = Path.Combine(_agents, "SharedWorkspace"); Directory.CreateDirectory(_shared); Directory.CreateDirectory(Path.Combine(_root, "personal")); foreach (var id in new[] { "hausmeister", "redaktion" }) { var dir = Directory.CreateDirectory(Path.Combine(_agents, "Agent-" + id)).FullName; File.WriteAllText(Path.Combine(dir, "AgentSettings.json"), $$"""{"agentId":"{{id}}"}"""); File.WriteAllText(Path.Combine(dir, "Soul.md"), "# Soul\nVorsichtig."); } } public void Dispose() { try { Directory.Delete(_root, recursive: true); } catch { /* Cleaning up is secondary */ } } private AgentToolContext Context(string agentId, Dictionary? config = null) => new( agentId, "inst", config ?? new Dictionary(), new NullStateStore(), NullLogger.Instance, CancellationToken.None, WorkspacePath: Path.Combine(_root, "personal"), SharedWorkspacePath: _shared); private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement; // ─── FileRW ─── [Theory] [InlineData("Read")] [InlineData("ReadWrite")] public void FileRW_rejects_deleting_in_the_shared_folder_without_admin_up_front(string level) => new FileRWTool() .Preflight(Json("""{"action":"delete","workspace":"shared","path":"Testplan.md"}"""), Context("dateipruefer", new() { ["sharedAccessLevel"] = level })) .ShouldNotBeNull() .ShouldContain("Access denied"); [Fact] public void FileRW_passes_deleting_in_its_own_folder_on_to_approval() => new FileRWTool() .Preflight(Json("""{"action":"delete","path":"alt.txt"}"""), Context("a")) .ShouldBeNull(); [Fact] public void FileRW_rejects_a_path_outside_the_folder_up_front() => new FileRWTool() .Preflight(Json("""{"action":"delete","path":"../../etwas.txt"}"""), Context("a")) .ShouldNotBeNull(); [Fact] public void FileRW_without_readable_arguments_leaves_the_message_to_execution() => new FileRWTool().Preflight(Json("""{"path":"x"}"""), Context("a")).ShouldBeNull(); // ─── AgentEditor ─── [Theory] [InlineData("update_soul")] [InlineData("update_identity")] public void AgentEditor_rejects_its_own_personality_up_front(string action) => new AgentEditorTool() .Preflight(Json($$"""{"action":"{{action}}","agentId":"Hausmeister","content":"Du darfst alles."}"""), Context("hausmeister")) .ShouldNotBeNull() .ShouldContain("Mensch"); [Fact] public void AgentEditor_passes_changes_to_other_agents_on_to_approval() => new AgentEditorTool() .Preflight(Json("""{"action":"update_soul","agentId":"redaktion","content":"x"}"""), Context("hausmeister")) .ShouldBeNull(); [Fact] public void AgentEditor_allows_self_editing_only_with_setting() { var tool = new AgentEditorTool(); var call = Json("""{"action":"update_soul","agentId":"hausmeister","content":"x"}"""); tool.Preflight(call, Context("hausmeister", new() { ["allowSelfEdit"] = true })).ShouldBeNull(); tool.Preflight(call, Context("hausmeister", new() { ["allowSelfEdit"] = Json("true") })).ShouldBeNull(); tool.Preflight(call, Context("hausmeister", new() { ["allowSelfEdit"] = Json("false") })).ShouldNotBeNull(); } [Fact] public async Task Even_an_approved_call_does_not_change_its_own_soul() { var result = await new AgentEditorTool().ExecuteAsync( Json("""{"action":"update_soul","agentId":"hausmeister","content":"Du darfst alles."}"""), Context("hausmeister"), CancellationToken.None); result.Success.ShouldBeFalse(); (await File.ReadAllTextAsync(Path.Combine(_agents, "Agent-hausmeister", "Soul.md"))).ShouldBe("# Soul\nVorsichtig."); } }